config

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

alg (15407B)


      1 #if 0
      2 
      3 bin="$(mktemp)"
      4 cc -o "$bin" -x c "$(realpath $0)"
      5 "$bin"
      6 
      7 exit 0
      8 #endif
      9 
     10 /*
     11 TODO:
     12 - fix: move_and... should be able to read tokens in quotes (with spaces)
     13 - readline capabilities
     14 */
     15 
     16 #include <stdint.h>
     17 #include <stdio.h>
     18 #include <stdlib.h>
     19 #include <string.h>
     20 #include <unistd.h>
     21 #include <sys/wait.h>
     22 
     23 #define MIN(a, b) ((a) < (b) ? (a) : (b))
     24 #define MAX(a, b) ((a) > (b) ? (a) : (b))
     25 
     26 #define LINEMAXLEN 10000
     27 
     28 #define ALGFILE "/home/sebastiano/box/speedcubing/bld/algs/algs.txt"
     29 #if 0
     30 #define INFILE "/home/sebastiano/box/speedcubing/bld/algs/algs.backup"
     31 #else
     32 #define INFILE ALGFILE
     33 #endif
     34 #define EDITOR "vi"
     35 
     36 char *alias[256][10] = {
     37 	['a'] = { "FU", "FUL", "FLU", "Fu", "Ful", "Flu", "FUl", NULL },
     38 	['b'] = { "UF", "UBL", "ULB", "Ur", "Ufr", "Ulb", "URb", NULL },
     39 	['c'] = { "UL", "UFL", "ULF", "Ul", "Ufl", "Ulf", "ULf",
     40 	    "corners", NULL },
     41 	['d'] = { "UB", "UBR", "URB", "Ub", "Ubr", "Urb", "UBl", NULL },
     42 	['e'] = { "FD", "FDR", "FRD", "Fd", "Frd", "Fdr", "FDr",
     43 	    "edges", NULL },
     44 	['f'] = { "RD", "RBD", "RDB", "Rd", "Rdb", "Rbd", "RDb",
     45 	    "2flips", NULL },
     46 	['g'] = { "LD", "LDF", "LFD", "Ld", "Ldf", "Lfd", "LDf", NULL },
     47 	['h'] = { NULL },
     48 	['i'] = { "FR", "FUR", "FRU", "Fr", "Fur", "Fru", "FRu", NULL },
     49 	['j'] = { "LU", "LUF", "LFU", "Lu", "Luf", "Lfu", "LUb", NULL },
     50 	['k'] = { "RB", "RUB", "RBU", "Rb", "Rub", "Rbu", "RBu", NULL },
     51 	['l'] = { "DF", "DFR", "DRF", "Df", "Dfr", "Drf", "DFl", NULL },
     52 	['m'] = { "DB", "DBR", "DRB", "Db", "Dbr", "Drb", "DBr",
     53 	    "midges", NULL },
     54 	['n'] = { "DR", "DFL", "DLF", "Dr", "Dfl", "Dlf", "DRf", NULL },
     55 	['o'] = { "FL", "FLD", "FDL", "Fl", "Fdl", "Fld", "FLd", NULL },
     56 	['p'] = { "DL", "DLB", "DBL", "Dl", "Dbl", "Dlb", "DLb",
     57 	    "parity", NULL },
     58 	['q'] = { NULL },
     59 	['r'] = { "BU", "BUR", "BRU", "Bu", "Bur", "Bru", "BUr", NULL },
     60 	['s'] = { "BD", "BUL", "BLU", "Bd", "Bul", "Blu", "BDl", NULL },
     61 	['t'] = { "BL", "BLD", "BDL", "Bl", "Bdl", "Bld", "BLu",
     62 	    "tcenters", NULL },
     63 	['u'] = { "BR", "BRD", "BDR", "Br", "Brd", "Bdr", "BRd", NULL },
     64 	['v'] = { "LF", "LUB", "LBU", "Lf", "Lub", "Lbu", "LFu", NULL },
     65 	['w'] = { "LB", "LBD", "LDB", "Lb", "Ldb", "Lbd", "LBd",
     66 	    "wings", NULL },
     67 	['x'] = { "RU", "RFU", "RUF", "Ru", "Rfu", "Ruf", "RUf",
     68 	    "xcenters", NULL },
     69 	['y'] = { "UR", "UFR", "URF", "Uf", "Ubl", "Ulb", "UFr", NULL },
     70 	['z'] = { "RF", "RDF", "RFD", "Rf", "Rdf", "Rfd", "RFd", NULL },
     71 	['3'] = { "3twists", NULL },
     72 };
     73 
     74 struct node {
     75 	char *key;
     76 	size_t mchildren;
     77 	size_t nchildren;
     78 	struct node *child;
     79 };
     80 
     81 enum command {
     82 	BACK,
     83 	EDIT,
     84 	EXPORT,
     85 	HELP,
     86 	PRINT,
     87 	QUIT,
     88 	RENAME,
     89 	NONE,
     90 	UNKNOWN
     91 };
     92 
     93 char command_aliases[] = {
     94 	[BACK] = 'b',
     95 	[EDIT] = 'e',
     96 	[EXPORT] = 'x',
     97 	[HELP] = 'h',
     98 	[PRINT] = 'p',
     99 	[QUIT] = 'q',
    100 	[RENAME] = 'r',
    101 };
    102 
    103 const char *HELPSTR =
    104 "This program can be used interactively to navigate and modify a collection\n"
    105 "of data organized as a tree. This data is saved to a file in a custom\n"
    106 "text-based format. At any given time, the program points to one node in the\n"
    107 "tree (the current node), and the user can give instructions.\n"
    108 "\n"
    109 "An instruction has the following syntax: [movement] [command]\n"
    110 "\n"
    111 "A movement can be specified either by a space-separated list of keys,\n"
    112 "where keys containing spaces must be wrapped in double quotes, or by a\n"
    113 "single word, each letter of which is translated to a key using the\n"
    114 "alias table. Only if a word does not match the key of any child of the\n"
    115 "current node is it interpreted as a list of aliases.\n"
    116 "\n"
    117 "Commands are a single letter preceded by a colon. For example ':q' quits\n"
    118 "the program. See below for a list of commands.\n"
    119 "\n"
    120 "If only a command is specified, it is exectued on the current node.\n"
    121 "\n"
    122 "If only a movement is specified, the current node is changed to the one\n"
    123 "specified by the movement. If the new current node is terminal, i.e. all\n"
    124 "its children are childless, a ':p' command is implied and is exectued as\n"
    125 "if 'movement :p' was given.\n"
    126 "\n"
    127 "If both a movement and a command are specified, the movement is applied,\n"
    128 "then the command is exectued, and finally the current node is changed\n"
    129 "back to the previous node.\n"
    130 "\n"
    131 "List of commands:\n"
    132 "  - :b Move back to the parent node (usually used without movement).\n"
    133 "  - :e Edit current node. Can be used to add or remove children.\n"
    134 "  - :x Export to csv.\n"
    135 "  - :h Print this help message.\n"
    136 "  - :p Print the key of the node.\n"
    137 "  - :q Quit (exit the program).\n"
    138 "  - :r Rename (edit the key of the current node).\n"
    139 ;
    140 
    141 int is_space(char c) {
    142 	return c == ' ' || c == '\t' || c == '\n';
    143 }
    144 
    145 int has_spaces(const char *k) {
    146 	for (const char *c = k; *c; c++)
    147 		if (is_space(*c))
    148 			return 1;
    149 	return 0;
    150 }
    151 
    152 int is_whitespace_only(const char *k) {
    153 	for (const char *c = k; *c; c++)
    154 		if (!is_space(*c))
    155 			return 0;
    156 	return 1;
    157 }
    158 
    159 void write_key(FILE *f, const char *k) {
    160 	if (has_spaces(k))
    161 		fprintf(f, "\"%s\"", k);
    162 	else
    163 		fprintf(f, "%s", k);
    164 }
    165 
    166 void write_indent(FILE *f, size_t n) {
    167 	for (size_t i = 0; i < n; i++)
    168 		fprintf(f, "  ");
    169 }
    170 
    171 int is_terminal(const struct node *n) {
    172 	for (size_t i = 0; i < n->nchildren; i++)
    173 		if (n->child[i].nchildren > 0)
    174 			return 0;
    175 	return 1;
    176 }
    177 
    178 void write_node(FILE *f, const struct node *n, size_t ntabs, int s) {
    179 	write_indent(f, ntabs);
    180 	fprintf(f, "{ ");
    181 	write_key(f, n->key);
    182 	if (is_terminal(n)) {
    183 		for (size_t i = 0; i < n->nchildren; i++) {
    184 			fprintf(f, " ");
    185 			write_key(f, n->child[i].key);
    186 		}
    187 		fprintf(f, " }\n");
    188 	} else {
    189 		fprintf(f, "\n");
    190 		for (size_t i = 0; i < n->nchildren; i++) {
    191 			if (s && !is_terminal(&n->child[i])) {
    192 				write_indent(f, ntabs+1);
    193 				fprintf(f, "{ ");
    194 				write_key(f, n->child[i].key);
    195 				fprintf(f, " { ... } }\n");
    196 			} else {
    197 				write_node(f, &n->child[i], ntabs+1, s);
    198 			}
    199 		}
    200 		write_indent(f, ntabs);
    201 		fprintf(f, "}\n");
    202 	}
    203 }
    204 
    205 void delete_node(struct node *n) {
    206 	if (n->child) {
    207 		for (size_t i = 0; i < n->nchildren; i++)
    208 			delete_node(&n->child[i]);
    209 		free(n->child);
    210 	}
    211 	n->nchildren = n->mchildren = 0;
    212 	free(n->key);
    213 }
    214 
    215 void grow_children_if_needed(struct node *n) {
    216 	if (n->nchildren == n->mchildren) {
    217 		size_t m = MAX(1, 2*n->mchildren);
    218 		n->child = realloc(n->child, m * sizeof(struct node));
    219 		memset(n->child + n->nchildren, 0,
    220 		    (m - n->nchildren) * sizeof(struct node));
    221 		n->mchildren = m;
    222 	}
    223 }
    224 
    225 void add_child(struct node *n, struct node c) {
    226 	grow_children_if_needed(n);
    227 	n->child[n->nchildren++] = c;
    228 }
    229 
    230 void parse_error(struct node *n, const char *a, size_t *i, size_t len) {
    231 	if (*i == len)
    232 		printf("Error: end of file reached unexpectedly.\n");
    233 	else
    234 		printf("Error: unexpected '%c' at position %zu.\n",
    235 		    a[*i], *i);
    236 
    237 	if (n != NULL) {
    238 		printf("Error occurred when reading node with key: ");
    239 		write_key(stdout, n->key);
    240 		printf("\n");
    241 	}
    242 }
    243 
    244 int read_key(struct node *n, const char *a, size_t *i, size_t len) {
    245 	while (is_space(a[*i])) (*i)++;
    246 	if (a[*i] == '{') {
    247 		printf("Error: missing key at %zu\n", *i);
    248 		return 0;
    249 	}
    250 	bool q = a[*i] == '"';
    251 	if (q) (*i)++;
    252 	size_t start = *i;
    253 	while (*i < len && ((q && a[*i] != '"') || (!q && !is_space(a[*i]))))
    254 		(*i)++;
    255 	size_t klen = *i - start;
    256 	n->key = malloc(klen+1);
    257 	memcpy(n->key, a+start, klen);
    258 	n->key[klen] = '\0';
    259 	if (*i == len) {
    260 		printf("Error: EOF in key starting at %zu "
    261 		       "(begins with '%.5s').\n", start, n->key);
    262 		return 0;
    263 	}
    264 	if (q)
    265 		(*i)++; // Skip quote
    266 	return 1;
    267 }
    268 
    269 int read_node(struct node *n, const char *a, size_t *i, size_t len) {
    270 	while (is_space(a[*i])) (*i)++;
    271 	if (a[*i] != '{') {
    272 		printf("Error: expected '{' at %zu, got '%c'.\n", *i, a[*i]);
    273 		return 0;
    274 	}
    275 	(*i)++;
    276 	if (!read_key(n, a, i, len))
    277 		return 0;
    278 	for (; *i < len; (*i)++) {
    279 		switch (a[*i]) {
    280 		case ' ':
    281 		case '\t':
    282 		case '\n':
    283 			continue;
    284 		case '}':
    285 			(*i)++;
    286 			return 1;
    287 		case '{':
    288 			grow_children_if_needed(n);
    289 			read_node(&n->child[n->nchildren++], a, i, len);
    290 			break;
    291 		default:
    292 			grow_children_if_needed(n);
    293 			read_key(&n->child[n->nchildren++], a, i, len);
    294 		}
    295 	}
    296 	return 1;
    297 }
    298 
    299 int read_file(struct node *n) {
    300 	FILE *f = fopen(INFILE, "r");
    301 	fseek(f, 0, SEEK_END);
    302 	size_t fsize = ftell(f);
    303 	fseek(f, 0, SEEK_SET);
    304 
    305 	char *a = malloc(fsize);
    306 	fread(a, 1, fsize, f);
    307 
    308 	size_t i = 0;
    309 	int result = read_node(n, a, &i, fsize);
    310 
    311 	free(a);
    312 	return result;
    313 }
    314 
    315 void write_tree(const struct node *root) {
    316 	FILE *f = fopen(ALGFILE, "w");
    317 	write_node(f, root, 0, 0);
    318 	/* printf("Data written to %s.\n", ALGFILE); */
    319 	fclose(f);
    320 }
    321 
    322 struct node *match_children(const char *w, size_t wlen, const struct node *n) {
    323 	for (size_t i = 0; i < n->nchildren; i++) {
    324 		if (strlen(n->child[i].key) != wlen)
    325 			continue;
    326 		if (!strncmp(w, n->child[i].key, wlen))
    327 			return &n->child[i];
    328 	}
    329 	return NULL;
    330 }
    331 
    332 int move_stack(const char *w, size_t wlen, struct node *stack[], size_t *top) {
    333 	struct node *c = match_children(w, wlen, stack[*top]);
    334 	if (c) {
    335 		stack[++(*top)] = c;
    336 		return 1;
    337 	}
    338 
    339 	int matched = 1;
    340 	for (size_t i = 0; matched && i < wlen; i++) {
    341 		matched = 0;
    342 		if ((w[i] < 'a' || w[i] > 'z') && (w[i] < '0' && w[i] > '9'))
    343 			return 1;
    344 		for (size_t j = 0; alias[(unsigned)w[i]][j]; j++) {
    345 			c = match_children(alias[(unsigned)w[i]][j],
    346 			    strlen(alias[(unsigned)w[i]][j]), stack[*top]);
    347 			if (c) {
    348 				stack[++(*top)] = c;
    349 				matched = 1;
    350 				break;
    351 			}
    352 		}
    353 	}
    354 	return matched;
    355 }
    356 
    357 int move_and_parse_command(const char *line,
    358     struct node *stack[], size_t *stack_top) {
    359 	size_t wstart, wend = 0;
    360 	while (line[wend] && line[wend] != ':') {
    361 		while (is_space(line[wend])) wend++;
    362 		wstart = wend;
    363 		while (!is_space(line[wend])) wend++;
    364 		if (!move_stack(line+wstart, wend-wstart, stack, stack_top)) {
    365 			printf("Invalid key '");
    366 			for (size_t i = wstart; i < wend; i++)
    367 				printf("%c", line[i]);
    368 			printf("'.\n");
    369 			return NONE;
    370 		}
    371 		while (is_space(line[wend])) wend++;
    372 	}
    373 
    374 	if (!line[wend])
    375 		return is_terminal(stack[*stack_top]) ? PRINT : NONE;
    376 	for (size_t i = 0; i < NONE; i++)
    377 		if (command_aliases[i] == line[wend+1])
    378 			return i;
    379 	printf("Unknown command.\n");
    380 	return UNKNOWN;
    381 }
    382 
    383 void print_node(struct node *n) {
    384 	if (is_terminal(n)) {
    385 		for (size_t i = 0; i < n->nchildren; i++)
    386 			printf("%s\n", n->child[i].key);
    387 	} else {
    388 		write_node(stdout, n, 0, 1);
    389 	}
    390 }
    391 
    392 void delete_child(struct node *n, size_t i) {
    393 	delete_node(&n->child[i]);
    394 	for (size_t j = i+1; j < n->nchildren; j++)
    395 		n->child[j-1] = n->child[j];
    396 	n->nchildren--;
    397 }
    398 
    399 void replace_child(struct node *n, size_t i, size_t j) {
    400 	if (i >= j) {
    401 		printf("Cannot replace child in wrong order.\n");
    402 		return;
    403 	}
    404 	delete_node(&n->child[j]);
    405 	n->child[j] = n->child[i];
    406 	n->nchildren--;
    407 	for (size_t k = i; k < n->nchildren; k++)
    408 		n->child[k] = n->child[k+1];
    409 }
    410 
    411 void remove_duplicate_children(struct node *n) {
    412 	for (size_t i = 0; i < n->nchildren; i++) {
    413 		for (size_t j = i + 1; j < n->nchildren; j++) {
    414 			if (!strcmp(n->child[i].key, n->child[j].key)) {
    415 				replace_child(n, i, j);
    416 				i--;
    417 				j--;
    418 				break;
    419 			}
    420 		}
    421 	}
    422 }
    423 
    424 int edit(struct node *n) {
    425 	/* create tmp file with algs, one by line */
    426 	char path[50] = "/tmp/alg.XXXXXXX";
    427 	int fd = mkstemp(path);
    428 	if (fd == -1) {
    429 		printf("Could not create tmp file for editing.\n");
    430 		return 0;
    431 	}
    432 	FILE *tmp = fdopen(fd, "w+");
    433 	for (size_t i = 0; i < n->nchildren; i++)
    434 		fprintf(tmp, "%s\n", n->child[i].key);
    435 	fclose(tmp);
    436 	close(fd);
    437 
    438 	/* fork and open editor */
    439 	int cpid = fork();
    440 	if (cpid == 0) {
    441 		execlp(EDITOR, EDITOR, path, (char *)NULL);
    442 	} else {
    443 		wait(NULL);
    444 		tmp = fopen(path, "r");
    445 		if (!tmp) {
    446 			printf("Could not read edited algs, aborting.\n");
    447 			return 0;
    448 		}
    449 
    450 		size_t oldn = n->nchildren;
    451 
    452 		/* Append the new children */
    453 		char line[LINEMAXLEN];
    454 		for (size_t i = 0; fgets(line, LINEMAXLEN, tmp); i++) {
    455 			size_t len = strlen(line);
    456 			struct node c = {0};
    457 			c.key = malloc(len);
    458 			memcpy(c.key, line, len);
    459 			c.key[len-1] = '\0';
    460 			add_child(n, c);
    461 		}
    462 		fclose(tmp);
    463 
    464 		/* Delete removed node, asking for confirmation */
    465 		for (size_t i = oldn-1; i != SIZE_MAX; i--) {
    466 			int found = 0;
    467 			for (size_t j = oldn; j < n->nchildren; j++)
    468 				found = found ||
    469 				    !strcmp(n->child[i].key, n->child[j].key);
    470 			if (!found) {
    471 				int d = 1;
    472 				if (!is_terminal(n)) {
    473 					printf("Confirm delete node ");
    474 					fflush(stdout);
    475 					write_key(stdout, n->child[i].key);
    476 					printf("? [y/N] ");
    477 					fgets(line, LINEMAXLEN, stdin);
    478 					d = line[0] == 'y' || line[0] == 'Y';
    479 				}
    480 				if (d) {
    481 					delete_child(n, i);
    482 					oldn--;
    483 				}
    484 			}
    485 		}
    486 
    487 		remove_duplicate_children(n);
    488 	}
    489 
    490 	return 1;
    491 }
    492 
    493 int is_table(const struct node *n) {
    494 	return n->nchildren != 0 &&
    495 	       n->child[0].nchildren != 0 &&
    496 	       n->child[0].child[0].nchildren != 0 &&
    497 	       is_terminal(&(n->child[0].child[0]));
    498 }
    499 
    500 struct node *find_child(const struct node *n, const char *k) {
    501 	for (size_t i = 0; i < n->nchildren; i++) {
    502 		if (!strcmp(n->child[i].key, k))
    503 			return &n->child[i];
    504 	}
    505 	return NULL;
    506 }
    507 
    508 void export(const struct node *n) {
    509 	if (!is_table(n)) {
    510 		printf("Cannot export: not a table.\n");
    511 		return;
    512 	}
    513 
    514 	char fname[50];
    515 	sprintf(fname, "%.45s.csv", n->key);
    516 	FILE *f = fopen(fname, "w");
    517 
    518 	/* Print first row (header) */
    519 	fprintf(f, "\"\",");
    520 	for (size_t i = 0; i < n->nchildren; i++)
    521 		fprintf(f, "\"%s\",", n->child[i].key);
    522 	fprintf(f, "\n");
    523 
    524 	/* Fill the rest of the table */
    525 	for (size_t i = 0; i < n->nchildren; i++) {
    526 		fprintf(f, "\"%s\",", n->child[i].key);
    527 		for (size_t j = 0; j < n->nchildren; j++) {
    528 			const struct node *c = find_child(
    529 			    &n->child[i], n->child[j].key);
    530 			if (c == NULL || c->nchildren == 0)
    531 				fprintf(f, "\"\",");
    532 			else
    533 				fprintf(f, "\"%s\",", c->child[0].key);
    534 		}
    535 		fprintf(f, "\n");
    536 	}
    537 
    538 	fclose(f);
    539 }
    540 
    541 int rename_node(struct node *n) {
    542 	printf("Old key: ");
    543 	write_key(stdout, n->key);
    544 	printf("\nNew key (empty line to cancel): ");
    545 
    546 	char k[LINEMAXLEN];
    547 	size_t len = fgets(k, LINEMAXLEN, stdin) == NULL ? 0 : strlen(k)-1;
    548 	k[len] = '\0'; /* Replace \n with \0 */
    549 	if (len == 0) {
    550 		printf("\nRenaming cancelled, old key kept.\n");
    551 		return 0;
    552 	} else {
    553 		if (strlen(n->key) < len)
    554 			n->key = realloc(n->key, len+1);
    555 		memcpy(n->key, k, len+1);
    556 		return 1;
    557 	}
    558 }
    559 
    560 void main_loop(struct node *root) {
    561 	char line[LINEMAXLEN];
    562 	struct node *stack[20] = { root };
    563 	size_t stack_top = 0;
    564 	while (1) {
    565 		for (size_t i = 0; i <= stack_top; i++) {
    566 			write_key(stdout, stack[i]->key);
    567 			printf(" ");
    568 		}
    569 		printf("> ");
    570 		fflush(stdout);
    571 		if (fgets(line, LINEMAXLEN, stdin) == NULL) {
    572 			printf("\n");
    573 			return;
    574 		}
    575 		if (is_whitespace_only(line)) continue;
    576 		int oldtop = stack_top;
    577 		int command = move_and_parse_command(line, stack, &stack_top);
    578 
    579 		switch (command) {
    580 			case BACK:
    581 				if (stack_top > 0)
    582 					oldtop = --stack_top;
    583 				else
    584 					printf("Already at top of stack.\n");
    585 				break;
    586 			case EDIT:
    587 				if(edit(stack[stack_top]))
    588 					write_tree(root);
    589 				break;
    590 			case EXPORT:
    591 				export(stack[stack_top]);
    592 				break;
    593 			case HELP:
    594 				printf("%s", HELPSTR);
    595 				break;
    596 			case PRINT:
    597 				print_node(stack[stack_top]);
    598 				break;
    599 			case QUIT:
    600 				return;
    601 			case RENAME:
    602 				if (rename_node(stack[stack_top]))
    603 					write_tree(root);
    604 				break;
    605 			case NONE:
    606 				oldtop = stack_top;
    607 			case UNKNOWN:
    608 			default:
    609 				break;
    610 		}
    611 		stack_top = oldtop;
    612 	}
    613 }
    614 
    615 int main() {
    616 	struct node root = {0};
    617 	if (!read_file(&root)) {
    618 		printf("Error reading data from file.\n");
    619 		return 1;
    620 	}
    621 	printf("Data read from %s\n", INFILE);
    622 
    623 	main_loop(&root);
    624 
    625 	delete_node(&root);
    626 	return 0;
    627 }