dtree.c (9165B)
1 /* This program is licensed under the terms of GNU GPL v3 or (at your option) 2 * any later version. Copyright (C) 2023-2026 Страхиња Радић. 3 * See the file LICENSE for exact copyright and license details. */ 4 5 #include <assert.h> 6 #include <errno.h> 7 #include <limits.h> 8 #include <stdarg.h> 9 #include <stdio.h> 10 #include <stdlib.h> 11 #include <string.h> 12 #include <sys/stat.h> 13 #include <unistd.h> 14 15 #include "utf8.h" 16 #include "version.h" 17 18 #define LINE_DEFAULT 85 19 #define LINE_ALLOC_DELTA ((96 - LINE_DEFAULT) + 96) 20 #define COPYRIGHT \ 21 (" This program is licensed under the terms of GNU GPL v3" \ 22 " or (at your option)\n" \ 23 " any later version. Copyright (C) 2023-2026 Strahinya Radich.\n" \ 24 " See the file LICENSE for exact copyright and license " \ 25 "details.") 26 #define U32(c) ((u32)(0x00000000 | (c))) 27 28 struct Node { 29 int level; 30 char* label; 31 32 struct Node* parent; 33 34 int children_count; 35 struct Node* children; 36 struct Node* last_child; 37 38 struct Node* next; 39 }; 40 41 enum { TREE_SYMBOLS_ASCII, TREE_SYMBOLS_SINGLE, TREE_SYMBOLS_DOUBLE }; 42 43 static struct Node* alloc_node(void); 44 static void cleanup(void); 45 static int error(const int code, const char* fmt, ...); 46 static void free_node(struct Node** root); 47 static void insert_node(struct Node** root, const int level, const char* label); 48 static void print_indent(void); 49 static void print_node(const struct Node* node); 50 static void set_node(struct Node* node, const int level, const char* label); 51 static void usage(void); 52 static void version(const int full); 53 static void warning(const char* fmt, ...); 54 55 /* clang-format off */ 56 static const int string_to_symbol_set[] = { 57 ['a'] = TREE_SYMBOLS_ASCII, 58 ['d'] = TREE_SYMBOLS_DOUBLE, 59 ['s'] = TREE_SYMBOLS_SINGLE 60 }; 61 static const u32 tree_symbols[][5] = { 62 [TREE_SYMBOLS_ASCII] = { 63 U32('`'), U32('+'), U32('-'), 64 U32('|'), U32(' ') 65 }, 66 [TREE_SYMBOLS_SINGLE] = { 67 U32(0x2514), U32(0x251c), U32(0x2500), 68 U32(0x2502), U32(' ') 69 }, 70 [TREE_SYMBOLS_DOUBLE] = { 71 U32(0x255a), U32(0x2560), U32(0x2550), 72 U32(0x2551), U32(' ') 73 } 74 }; 75 /* clang-format on */ 76 77 static struct Node* root = NULL; 78 static u32* indent = NULL; 79 static u32* pindent = NULL; 80 static int symbol_set = TREE_SYMBOLS_SINGLE; 81 82 static struct Node* 83 alloc_node(void) 84 { 85 struct Node* newnode = NULL; 86 87 newnode = malloc(sizeof(struct Node)); 88 if (!newnode) 89 { 90 perror(PROGRAMNAME ": malloc"); 91 exit(errno); 92 } 93 94 assert(newnode != NULL); 95 newnode->level = 0; 96 newnode->label = NULL; 97 newnode->parent = NULL; 98 newnode->children = NULL; 99 newnode->children_count = 0; 100 newnode->last_child = NULL; 101 newnode->next = NULL; 102 103 return newnode; 104 } 105 106 static void 107 cleanup(void) 108 { 109 free_node(&root); 110 /* free(root); */ 111 free(indent); 112 } 113 114 static int 115 error(const int code, const char* fmt, ...) 116 { 117 va_list args; 118 char buf[LINE_DEFAULT]; 119 va_start(args, fmt); 120 if (vsnprintf(buf, sizeof buf, fmt, args) >= (int)sizeof buf) 121 buf[sizeof buf - 1] = 0; 122 va_end(args); 123 fprintf(stderr, "%s: %s\n", PROGRAMNAME, buf); 124 return code; 125 } 126 127 static void 128 free_node(struct Node** root) 129 { 130 if (!root || !*root) 131 return; 132 133 if ((*root)->label) 134 free((*root)->label); 135 136 if ((*root)->next) 137 free_node(&(*root)->next); 138 139 if ((*root)->children) 140 free_node(&(*root)->children); 141 142 free(*root); 143 } 144 145 static void 146 insert_node(struct Node** root, const int level, const char* label) 147 { 148 if (!root || !*root) 149 return; 150 151 if ((*root)->level == level) 152 { 153 struct Node* newnode = NULL; 154 155 newnode = alloc_node(); 156 set_node(newnode, level, label); 157 158 if (!(*root)->next) 159 (*root)->next = newnode; 160 else 161 { 162 struct Node* last = NULL; 163 last = (*root)->next; 164 while (last && last->next) 165 last = last->next; 166 167 last->next = newnode; 168 } 169 (*root)->last_child = newnode; 170 } 171 else if ((*root)->level + 1 < level) 172 { 173 if (!(*root)->last_child) 174 exit(error(1, 175 "Fatal: inserting node '%s' >1" 176 " levels below '%s'", 177 label, (*root)->label)); 178 179 insert_node(&(*root)->last_child, level, label); 180 } 181 else 182 { 183 struct Node* newnode = NULL; 184 185 newnode = alloc_node(); 186 set_node(newnode, level, label); 187 newnode->parent = *root; 188 189 (*root)->children_count++; 190 191 if (!(*root)->children) 192 (*root)->children = newnode; 193 else 194 { 195 struct Node* last = NULL; 196 last = (*root)->children; 197 while (last && last->next) 198 last = last->next; 199 200 last->next = newnode; 201 } 202 203 (*root)->last_child = newnode; 204 } 205 } 206 207 static void 208 print_indent(void) 209 { 210 const u32* ppindent = indent; 211 u8 u8ch[8]; 212 size_t len; 213 214 while (*ppindent) 215 { 216 len = u32_rune_to_u8(u8ch, *ppindent); 217 u8ch[len] = 0; 218 printf("%s ", u8ch); 219 ppindent++; 220 } 221 } 222 223 static void 224 print_node(const struct Node* node) 225 { 226 if (!node) 227 return; 228 229 print_indent(); 230 231 if (node->level > 0) 232 { 233 u8 u8ch[8]; 234 size_t len; 235 236 if (!node->next) 237 len = u32_rune_to_u8(u8ch, tree_symbols[symbol_set][0]); 238 else 239 len = u32_rune_to_u8(u8ch, tree_symbols[symbol_set][1]); 240 u8ch[len] = 0; 241 printf("%s", u8ch); 242 len = u32_rune_to_u8(u8ch, tree_symbols[symbol_set][2]); 243 u8ch[len] = 0; 244 printf("%s%s", u8ch, u8ch); 245 } 246 printf("%s\n", node->label ? node->label : "<NULL>"); 247 248 if (node->children) 249 { 250 if (node->level > 0) 251 *pindent++ = node->next ? tree_symbols[symbol_set][3] 252 : tree_symbols[symbol_set][4]; 253 *pindent = 0; 254 255 print_node(node->children); 256 257 if (node->level > 0) 258 pindent--; 259 *pindent = 0; 260 } 261 262 if (node->next) 263 print_node(node->next); 264 } 265 266 static void 267 set_node(struct Node* node, const int level, const char* label) 268 { 269 size_t size; 270 size = strlen(label) + 1; 271 if (!node->label) 272 { 273 node->label = malloc(size); 274 if (!node->label) 275 { 276 perror(PROGRAMNAME ": malloc"); 277 exit(errno); 278 } 279 } 280 if (!memccpy(node->label, label, 0, size)) 281 node->label[size - 1] = 0; 282 node->level = level; 283 } 284 285 static void 286 usage(void) 287 { 288 printf("Usage:\t%s -hVv\n\t%s [-s a|d|s] [file]\n", PROGRAMNAME, 289 PROGRAMNAME); 290 } 291 292 static void 293 version(const int full) 294 { 295 printf("%s %s, committed on %s\n", PROGRAMNAME, VERSION, DATE); 296 if (full) 297 puts(COPYRIGHT); 298 } 299 300 static void 301 warning(const char* fmt, ...) 302 { 303 va_list args; 304 char buf[LINE_DEFAULT]; 305 va_start(args, fmt); 306 if (vsnprintf(buf, sizeof buf, fmt, args) >= (int)sizeof buf) 307 buf[sizeof buf - 1] = 0; 308 va_end(args); 309 fprintf(stderr, "%s: %s\n", PROGRAMNAME, buf); 310 } 311 312 int 313 main(int argc, char** argv) 314 { 315 struct stat st; 316 FILE* input = NULL; 317 char* line = NULL; 318 char* pline = NULL; 319 char* tpline = NULL; /* Temporary */ 320 char* eol = NULL; 321 ssize_t line_delta; 322 ssize_t line_size; 323 int ch; 324 int level = 0; 325 326 #ifdef __OpenBSD__ 327 if (pledge("stdio rpath unveil", NULL) < 0) 328 { 329 perror(PROGRAMNAME ": pledge"); 330 exit(errno); 331 } 332 #endif 333 334 if ((ch = getopt(argc, argv, ":hs:Vv")) != -1) 335 { 336 switch (ch) 337 { 338 case 'h': 339 usage(); 340 exit(0); 341 case 's': 342 if (!strchr("ads", *optarg)) 343 exit(error(1, "Invalid symbol set: '%s'", 344 optarg)); 345 assert((*optarg == 'a') || (*optarg == 'd') 346 || (*optarg == 's')); 347 symbol_set = string_to_symbol_set[(int)*optarg]; 348 break; 349 case 'V': 350 version(1); 351 exit(0); 352 case 'v': 353 version(0); 354 exit(0); 355 case ':': 356 exit(error(1, "Argument expected for '-%c'", optopt)); 357 case '?': 358 default: 359 exit(error(1, "Invalid parameter '-%c'", optopt)); 360 } 361 } 362 363 if (argc > optind + 1) 364 warning("Additional argument(s) ignored: '%s ...'", 365 argv[optind + 1]); 366 if (argc <= optind) 367 input = stdin; 368 else 369 { 370 #ifdef __OpenBSD__ 371 if (unveil(argv[optind], "r") < 0) 372 { 373 perror(PROGRAMNAME ": unveil"); 374 exit(errno); 375 } 376 if (unveil(NULL, NULL) < 0) 377 { 378 perror(PROGRAMNAME ": unveil"); 379 exit(errno); 380 } 381 #endif 382 if (stat(argv[optind], &st) < 0) 383 { 384 perror(PROGRAMNAME ": stat"); 385 exit(errno); 386 } 387 if (!S_ISREG(st.st_mode)) 388 exit(error(EINVAL, "Is not a regular file")); 389 390 if (!(input = fopen(argv[optind], "r"))) 391 { 392 perror(PROGRAMNAME ": fopen"); 393 exit(errno); 394 } 395 } 396 397 errno = 0; 398 line_delta = sysconf(_SC_LINE_MAX); 399 if (line_delta == -1) 400 { 401 if (errno) 402 { 403 perror(PROGRAMNAME ": sysconf"); 404 exit(errno); 405 } 406 line_delta = _POSIX2_LINE_MAX; 407 } 408 line_size = line_delta; 409 line = malloc(line_size); 410 pline = line; 411 do_input: 412 if (feof(input)) 413 goto done_input; 414 assert((pline - line >= 0) && (pline - line < line_size)); 415 if (!fgets(pline, line_size - (pline - line), input)) 416 goto done_input; 417 418 eol = strchr(line, '\n'); 419 if (eol) 420 *eol = 0; 421 else 422 { 423 line_size += line_delta; 424 if (!(tpline = realloc(line, line_size))) 425 { 426 perror(PROGRAMNAME ": realloc"); 427 exit(errno); 428 } 429 line = tpline; 430 pline = line + (line_size - line_delta - 1); 431 goto do_input; 432 } 433 434 pline = line; 435 level = 0; 436 while (*pline) 437 { 438 if (*pline == '\t') 439 level++; 440 else 441 { 442 if (!root) 443 { 444 root = alloc_node(); 445 set_node(root, level, pline); 446 } 447 else 448 insert_node(&root, level, pline); 449 break; 450 } 451 pline++; 452 } 453 pline = line; 454 goto do_input; 455 456 done_input: 457 free(line); 458 atexit(&cleanup); 459 if (input != stdin) 460 fclose(input); 461 462 indent = calloc(line_size, sizeof(u32)); 463 if (!indent) 464 { 465 perror(PROGRAMNAME ": calloc"); 466 exit(errno); 467 } 468 pindent = indent; 469 470 print_node(root); 471 472 return 0; 473 }