dtree

Програм из командне линије за исцртавање стабала
git clone https://git.sr.ht/~strahinja/dtree
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

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 }