config

My configuration files and custom scripts.
git clone https://git.tronto.net/config
Download | Log | Files | Refs

commit 0a1512c2308dc47f114c693183aec6b277afd269
parent f8ebfb76245555f8531f591054e923f53e90b91d
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date:   Sat,  9 May 2026 15:42:19 +0200

Reintroduce xedit, move alg to scripts

Diffstat:
Ascripts/alg | 627+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/xedit | 9+++++++++
2 files changed, 636 insertions(+), 0 deletions(-)

diff --git a/scripts/alg b/scripts/alg @@ -0,0 +1,627 @@ +#if 0 + +bin="$(mktemp)" +cc -o "$bin" -x c "$(realpath $0)" +"$bin" + +exit 0 +#endif + +/* +TODO: +- fix: move_and... should be able to read tokens in quotes (with spaces) +- readline capabilities +*/ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/wait.h> + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +#define LINEMAXLEN 10000 + +#define ALGFILE "/home/sebastiano/box/speedcubing/bld/algs/algs.txt" +#if 0 +#define INFILE "/home/sebastiano/box/speedcubing/bld/algs/algs.backup" +#else +#define INFILE ALGFILE +#endif +#define EDITOR "vi" + +char *alias[256][10] = { + ['a'] = { "FU", "FUL", "FLU", "Fu", "Ful", "Flu", "FUl", NULL }, + ['b'] = { "UF", "UBL", "ULB", "Ur", "Ufr", "Ulb", "URb", NULL }, + ['c'] = { "UL", "UFL", "ULF", "Ul", "Ufl", "Ulf", "ULf", + "corners", NULL }, + ['d'] = { "UB", "UBR", "URB", "Ub", "Ubr", "Urb", "UBl", NULL }, + ['e'] = { "FD", "FDR", "FRD", "Fd", "Frd", "Fdr", "FDr", + "edges", NULL }, + ['f'] = { "RD", "RBD", "RDB", "Rd", "Rdb", "Rbd", "RDb", + "2flips", NULL }, + ['g'] = { "LD", "LDF", "LFD", "Ld", "Ldf", "Lfd", "LDf", NULL }, + ['h'] = { NULL }, + ['i'] = { "FR", "FUR", "FRU", "Fr", "Fur", "Fru", "FRu", NULL }, + ['j'] = { "LU", "LUF", "LFU", "Lu", "Luf", "Lfu", "LUb", NULL }, + ['k'] = { "RB", "RUB", "RBU", "Rb", "Rub", "Rbu", "RBu", NULL }, + ['l'] = { "DF", "DFR", "DRF", "Df", "Dfr", "Drf", "DFl", NULL }, + ['m'] = { "DB", "DBR", "DRB", "Db", "Dbr", "Drb", "DBr", + "midges", NULL }, + ['n'] = { "DR", "DFL", "DLF", "Dr", "Dfl", "Dlf", "DRf", NULL }, + ['o'] = { "FL", "FLD", "FDL", "Fl", "Fdl", "Fld", "FLd", NULL }, + ['p'] = { "DL", "DLB", "DBL", "Dl", "Dbl", "Dlb", "DLb", + "parity", NULL }, + ['q'] = { NULL }, + ['r'] = { "BU", "BUR", "BRU", "Bu", "Bur", "Bru", "BUr", NULL }, + ['s'] = { "BD", "BUL", "BLU", "Bd", "Bul", "Blu", "BDl", NULL }, + ['t'] = { "BL", "BLD", "BDL", "Bl", "Bdl", "Bld", "BLu", + "tcenters", NULL }, + ['u'] = { "BR", "BRD", "BDR", "Br", "Brd", "Bdr", "BRd", NULL }, + ['v'] = { "LF", "LUB", "LBU", "Lf", "Lub", "Lbu", "LFu", NULL }, + ['w'] = { "LB", "LBD", "LDB", "Lb", "Ldb", "Lbd", "LBd", + "wings", NULL }, + ['x'] = { "RU", "RFU", "RUF", "Ru", "Rfu", "Ruf", "RUf", + "xcenters", NULL }, + ['y'] = { "UR", "UFR", "URF", "Uf", "Ubl", "Ulb", "UFr", NULL }, + ['z'] = { "RF", "RDF", "RFD", "Rf", "Rdf", "Rfd", "RFd", NULL }, + ['3'] = { "3twists", NULL }, +}; + +struct node { + char *key; + size_t mchildren; + size_t nchildren; + struct node *child; +}; + +enum command { + BACK, + EDIT, + EXPORT, + HELP, + PRINT, + QUIT, + RENAME, + NONE, + UNKNOWN +}; + +char command_aliases[] = { + [BACK] = 'b', + [EDIT] = 'e', + [EXPORT] = 'x', + [HELP] = 'h', + [PRINT] = 'p', + [QUIT] = 'q', + [RENAME] = 'r', +}; + +const char *HELPSTR = +"This program can be used interactively to navigate and modify a collection\n" +"of data organized as a tree. This data is saved to a file in a custom\n" +"text-based format. At any given time, the program points to one node in the\n" +"tree (the current node), and the user can give instructions.\n" +"\n" +"An instruction has the following syntax: [movement] [command]\n" +"\n" +"A movement can be specified either by a space-separated list of keys,\n" +"where keys containing spaces must be wrapped in double quotes, or by a\n" +"single word, each letter of which is translated to a key using the\n" +"alias table. Only if a word does not match the key of any child of the\n" +"current node is it interpreted as a list of aliases.\n" +"\n" +"Commands are a single letter preceded by a colon. For example ':q' quits\n" +"the program. See below for a list of commands.\n" +"\n" +"If only a command is specified, it is exectued on the current node.\n" +"\n" +"If only a movement is specified, the current node is changed to the one\n" +"specified by the movement. If the new current node is terminal, i.e. all\n" +"its children are childless, a ':p' command is implied and is exectued as\n" +"if 'movement :p' was given.\n" +"\n" +"If both a movement and a command are specified, the movement is applied,\n" +"then the command is exectued, and finally the current node is changed\n" +"back to the previous node.\n" +"\n" +"List of commands:\n" +" - :b Move back to the parent node (usually used without movement).\n" +" - :e Edit current node. Can be used to add or remove children.\n" +" - :x Export to csv.\n" +" - :h Print this help message.\n" +" - :p Print the key of the node.\n" +" - :q Quit (exit the program).\n" +" - :r Rename (edit the key of the current node).\n" +; + +int is_space(char c) { + return c == ' ' || c == '\t' || c == '\n'; +} + +int has_spaces(const char *k) { + for (const char *c = k; *c; c++) + if (is_space(*c)) + return 1; + return 0; +} + +int is_whitespace_only(const char *k) { + for (const char *c = k; *c; c++) + if (!is_space(*c)) + return 0; + return 1; +} + +void write_key(FILE *f, const char *k) { + if (has_spaces(k)) + fprintf(f, "\"%s\"", k); + else + fprintf(f, "%s", k); +} + +void write_indent(FILE *f, size_t n) { + for (size_t i = 0; i < n; i++) + fprintf(f, " "); +} + +int is_terminal(const struct node *n) { + for (size_t i = 0; i < n->nchildren; i++) + if (n->child[i].nchildren > 0) + return 0; + return 1; +} + +void write_node(FILE *f, const struct node *n, size_t ntabs, int s) { + write_indent(f, ntabs); + fprintf(f, "{ "); + write_key(f, n->key); + if (is_terminal(n)) { + for (size_t i = 0; i < n->nchildren; i++) { + fprintf(f, " "); + write_key(f, n->child[i].key); + } + fprintf(f, " }\n"); + } else { + fprintf(f, "\n"); + for (size_t i = 0; i < n->nchildren; i++) { + if (s && !is_terminal(&n->child[i])) { + write_indent(f, ntabs+1); + fprintf(f, "{ "); + write_key(f, n->child[i].key); + fprintf(f, " { ... } }\n"); + } else { + write_node(f, &n->child[i], ntabs+1, s); + } + } + write_indent(f, ntabs); + fprintf(f, "}\n"); + } +} + +void delete_node(struct node *n) { + if (n->child) { + for (size_t i = 0; i < n->nchildren; i++) + delete_node(&n->child[i]); + free(n->child); + } + n->nchildren = n->mchildren = 0; + free(n->key); +} + +void grow_children_if_needed(struct node *n) { + if (n->nchildren == n->mchildren) { + size_t m = MAX(1, 2*n->mchildren); + n->child = realloc(n->child, m * sizeof(struct node)); + memset(n->child + n->nchildren, 0, + (m - n->nchildren) * sizeof(struct node)); + n->mchildren = m; + } +} + +void add_child(struct node *n, struct node c) { + grow_children_if_needed(n); + n->child[n->nchildren++] = c; +} + +void parse_error(struct node *n, const char *a, size_t *i, size_t len) { + if (*i == len) + printf("Error: end of file reached unexpectedly.\n"); + else + printf("Error: unexpected '%c' at position %zu.\n", + a[*i], *i); + + if (n != NULL) { + printf("Error occurred when reading node with key: "); + write_key(stdout, n->key); + printf("\n"); + } +} + +int read_key(struct node *n, const char *a, size_t *i, size_t len) { + while (is_space(a[*i])) (*i)++; + if (a[*i] == '{') { + printf("Error: missing key at %zu\n", *i); + return 0; + } + bool q = a[*i] == '"'; + if (q) (*i)++; + size_t start = *i; + while (*i < len && ((q && a[*i] != '"') || (!q && !is_space(a[*i])))) + (*i)++; + size_t klen = *i - start; + n->key = malloc(klen+1); + memcpy(n->key, a+start, klen); + n->key[klen] = '\0'; + if (*i == len) { + printf("Error: EOF in key starting at %zu " + "(begins with '%.5s').\n", start, n->key); + return 0; + } + if (q) + (*i)++; // Skip quote + return 1; +} + +int read_node(struct node *n, const char *a, size_t *i, size_t len) { + while (is_space(a[*i])) (*i)++; + if (a[*i] != '{') { + printf("Error: expected '{' at %zu, got '%c'.\n", *i, a[*i]); + return 0; + } + (*i)++; + if (!read_key(n, a, i, len)) + return 0; + for (; *i < len; (*i)++) { + switch (a[*i]) { + case ' ': + case '\t': + case '\n': + continue; + case '}': + (*i)++; + return 1; + case '{': + grow_children_if_needed(n); + read_node(&n->child[n->nchildren++], a, i, len); + break; + default: + grow_children_if_needed(n); + read_key(&n->child[n->nchildren++], a, i, len); + } + } + return 1; +} + +int read_file(struct node *n) { + FILE *f = fopen(INFILE, "r"); + fseek(f, 0, SEEK_END); + size_t fsize = ftell(f); + fseek(f, 0, SEEK_SET); + + char *a = malloc(fsize); + fread(a, 1, fsize, f); + + size_t i = 0; + int result = read_node(n, a, &i, fsize); + + free(a); + return result; +} + +void write_tree(const struct node *root) { + FILE *f = fopen(ALGFILE, "w"); + write_node(f, root, 0, 0); + /* printf("Data written to %s.\n", ALGFILE); */ + fclose(f); +} + +struct node *match_children(const char *w, size_t wlen, const struct node *n) { + for (size_t i = 0; i < n->nchildren; i++) { + if (strlen(n->child[i].key) != wlen) + continue; + if (!strncmp(w, n->child[i].key, wlen)) + return &n->child[i]; + } + return NULL; +} + +int move_stack(const char *w, size_t wlen, struct node *stack[], size_t *top) { + struct node *c = match_children(w, wlen, stack[*top]); + if (c) { + stack[++(*top)] = c; + return 1; + } + + int matched = 1; + for (size_t i = 0; matched && i < wlen; i++) { + matched = 0; + if ((w[i] < 'a' || w[i] > 'z') && (w[i] < '0' && w[i] > '9')) + return 1; + for (size_t j = 0; alias[(unsigned)w[i]][j]; j++) { + c = match_children(alias[(unsigned)w[i]][j], + strlen(alias[(unsigned)w[i]][j]), stack[*top]); + if (c) { + stack[++(*top)] = c; + matched = 1; + break; + } + } + } + return matched; +} + +int move_and_parse_command(const char *line, + struct node *stack[], size_t *stack_top) { + size_t wstart, wend = 0; + while (line[wend] && line[wend] != ':') { + while (is_space(line[wend])) wend++; + wstart = wend; + while (!is_space(line[wend])) wend++; + if (!move_stack(line+wstart, wend-wstart, stack, stack_top)) { + printf("Invalid key '"); + for (size_t i = wstart; i < wend; i++) + printf("%c", line[i]); + printf("'.\n"); + return NONE; + } + while (is_space(line[wend])) wend++; + } + + if (!line[wend]) + return is_terminal(stack[*stack_top]) ? PRINT : NONE; + for (size_t i = 0; i < NONE; i++) + if (command_aliases[i] == line[wend+1]) + return i; + printf("Unknown command.\n"); + return UNKNOWN; +} + +void print_node(struct node *n) { + if (is_terminal(n)) { + for (size_t i = 0; i < n->nchildren; i++) + printf("%s\n", n->child[i].key); + } else { + write_node(stdout, n, 0, 1); + } +} + +void delete_child(struct node *n, size_t i) { + delete_node(&n->child[i]); + for (size_t j = i+1; j < n->nchildren; j++) + n->child[j-1] = n->child[j]; + n->nchildren--; +} + +void replace_child(struct node *n, size_t i, size_t j) { + if (i >= j) { + printf("Cannot replace child in wrong order.\n"); + return; + } + delete_node(&n->child[j]); + n->child[j] = n->child[i]; + n->nchildren--; + for (size_t k = i; k < n->nchildren; k++) + n->child[k] = n->child[k+1]; +} + +void remove_duplicate_children(struct node *n) { + for (size_t i = 0; i < n->nchildren; i++) { + for (size_t j = i + 1; j < n->nchildren; j++) { + if (!strcmp(n->child[i].key, n->child[j].key)) { + replace_child(n, i, j); + i--; + j--; + break; + } + } + } +} + +int edit(struct node *n) { + /* create tmp file with algs, one by line */ + char path[50] = "/tmp/alg.XXXXXXX"; + int fd = mkstemp(path); + if (fd == -1) { + printf("Could not create tmp file for editing.\n"); + return 0; + } + FILE *tmp = fdopen(fd, "w+"); + for (size_t i = 0; i < n->nchildren; i++) + fprintf(tmp, "%s\n", n->child[i].key); + fclose(tmp); + close(fd); + + /* fork and open editor */ + int cpid = fork(); + if (cpid == 0) { + execlp(EDITOR, EDITOR, path, (char *)NULL); + } else { + wait(NULL); + tmp = fopen(path, "r"); + if (!tmp) { + printf("Could not read edited algs, aborting.\n"); + return 0; + } + + size_t oldn = n->nchildren; + + /* Append the new children */ + char line[LINEMAXLEN]; + for (size_t i = 0; fgets(line, LINEMAXLEN, tmp); i++) { + size_t len = strlen(line); + struct node c = {0}; + c.key = malloc(len); + memcpy(c.key, line, len); + c.key[len-1] = '\0'; + add_child(n, c); + } + fclose(tmp); + + /* Delete removed node, asking for confirmation */ + for (size_t i = oldn-1; i != SIZE_MAX; i--) { + int found = 0; + for (size_t j = oldn; j < n->nchildren; j++) + found = found || + !strcmp(n->child[i].key, n->child[j].key); + if (!found) { + int d = 1; + if (!is_terminal(n)) { + printf("Confirm delete node "); + fflush(stdout); + write_key(stdout, n->child[i].key); + printf("? [y/N] "); + fgets(line, LINEMAXLEN, stdin); + d = line[0] == 'y' || line[0] == 'Y'; + } + if (d) { + delete_child(n, i); + oldn--; + } + } + } + + remove_duplicate_children(n); + } + + return 1; +} + +int is_table(const struct node *n) { + return n->nchildren != 0 && + n->child[0].nchildren != 0 && + n->child[0].child[0].nchildren != 0 && + is_terminal(&(n->child[0].child[0])); +} + +struct node *find_child(const struct node *n, const char *k) { + for (size_t i = 0; i < n->nchildren; i++) { + if (!strcmp(n->child[i].key, k)) + return &n->child[i]; + } + return NULL; +} + +void export(const struct node *n) { + if (!is_table(n)) { + printf("Cannot export: not a table.\n"); + return; + } + + char fname[50]; + sprintf(fname, "%.45s.csv", n->key); + FILE *f = fopen(fname, "w"); + + /* Print first row (header) */ + fprintf(f, "\"\","); + for (size_t i = 0; i < n->nchildren; i++) + fprintf(f, "\"%s\",", n->child[i].key); + fprintf(f, "\n"); + + /* Fill the rest of the table */ + for (size_t i = 0; i < n->nchildren; i++) { + fprintf(f, "\"%s\",", n->child[i].key); + for (size_t j = 0; j < n->nchildren; j++) { + const struct node *c = find_child( + &n->child[i], n->child[j].key); + if (c == NULL || c->nchildren == 0) + fprintf(f, "\"\","); + else + fprintf(f, "\"%s\",", c->child[0].key); + } + fprintf(f, "\n"); + } + + fclose(f); +} + +int rename_node(struct node *n) { + printf("Old key: "); + write_key(stdout, n->key); + printf("\nNew key (empty line to cancel): "); + + char k[LINEMAXLEN]; + size_t len = fgets(k, LINEMAXLEN, stdin) == NULL ? 0 : strlen(k)-1; + k[len] = '\0'; /* Replace \n with \0 */ + if (len == 0) { + printf("\nRenaming cancelled, old key kept.\n"); + return 0; + } else { + if (strlen(n->key) < len) + n->key = realloc(n->key, len+1); + memcpy(n->key, k, len+1); + return 1; + } +} + +void main_loop(struct node *root) { + char line[LINEMAXLEN]; + struct node *stack[20] = { root }; + size_t stack_top = 0; + while (1) { + for (size_t i = 0; i <= stack_top; i++) { + write_key(stdout, stack[i]->key); + printf(" "); + } + printf("> "); + fflush(stdout); + if (fgets(line, LINEMAXLEN, stdin) == NULL) { + printf("\n"); + return; + } + if (is_whitespace_only(line)) continue; + int oldtop = stack_top; + int command = move_and_parse_command(line, stack, &stack_top); + + switch (command) { + case BACK: + if (stack_top > 0) + oldtop = --stack_top; + else + printf("Already at top of stack.\n"); + break; + case EDIT: + if(edit(stack[stack_top])) + write_tree(root); + break; + case EXPORT: + export(stack[stack_top]); + break; + case HELP: + printf("%s", HELPSTR); + break; + case PRINT: + print_node(stack[stack_top]); + break; + case QUIT: + return; + case RENAME: + if (rename_node(stack[stack_top])) + write_tree(root); + break; + case NONE: + oldtop = stack_top; + case UNKNOWN: + default: + break; + } + stack_top = oldtop; + } +} + +int main() { + struct node root = {0}; + if (!read_file(&root)) { + printf("Error reading data from file.\n"); + return 1; + } + printf("Data read from %s\n", INFILE); + + main_loop(&root); + + delete_node(&root); + return 0; +} diff --git a/scripts/xedit b/scripts/xedit @@ -0,0 +1,9 @@ +#!/bin/sh + +# My X editor +# Requires: terminal + +editor=${EDITOR:-${VISUAL:-vi}} +xed=${XEDIT:-"terminal "$editor""} + +$xed "$@"