diff options
Diffstat (limited to '')
| -rwxr-xr-x | scripts/alg | 627 | ||||
| -rwxr-xr-x | scripts/xedit | 9 |
2 files changed, 636 insertions, 0 deletions
diff --git a/scripts/alg b/scripts/alg new file mode 100755 index 0000000..54839b4 --- /dev/null +++ b/scripts/alg | |||
| @@ -0,0 +1,627 @@ | |||
| 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 | } | ||
diff --git a/scripts/xedit b/scripts/xedit new file mode 100755 index 0000000..3f68f24 --- /dev/null +++ b/scripts/xedit | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | |||
| 3 | # My X editor | ||
| 4 | # Requires: terminal | ||
| 5 | |||
| 6 | editor=${EDITOR:-${VISUAL:-vi}} | ||
| 7 | xed=${XEDIT:-"terminal "$editor""} | ||
| 8 | |||
| 9 | $xed "$@" | ||
