1 // Copyright (c) 2019 nyorain
2 // Distributed under the Boost Software License, Version 1.0.
3 // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt
4
5 #define _XOPEN_SOURCE 600
6 #define _POSIX_C_SOURCE 200809L
7 #define _WIN32_WINNT 0x0600
8
9 // Needed on windows so that we can use sprintf without warning.
10 #define _CRT_SECURE_NO_WARNINGS
11
12 #include <dlg/output.h>
13 #include <dlg/dlg.h>
14 #include <wchar.h>
15 #include <time.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19
20 const char* const dlg_reset_sequence = "\033[0m";
21 const struct dlg_style dlg_default_output_styles[] = {
22 {dlg_text_style_italic, dlg_color_green, dlg_color_none},
23 {dlg_text_style_dim, dlg_color_gray, dlg_color_none},
24 {dlg_text_style_none, dlg_color_cyan, dlg_color_none},
25 {dlg_text_style_none, dlg_color_yellow, dlg_color_none},
26 {dlg_text_style_none, dlg_color_red, dlg_color_none},
27 {dlg_text_style_bold, dlg_color_red, dlg_color_none}
28 };
29
30 static void* xalloc(size_t size) {
31 void* ret = calloc(size, 1);
32 if(!ret) fprintf(stderr, "dlg: calloc returned NULL, probably crashing (size: %zu)\n", size);
33 return ret;
34 }
35
36 static void* xrealloc(void* ptr, size_t size) {
37 void* ret = realloc(ptr, size);
38 if(!ret) fprintf(stderr, "dlg: realloc returned NULL, probably crashing (size: %zu)\n", size);
39 return ret;
40 }
41
42 struct dlg_tag_func_pair {
43 const char* tag;
44 const char* func;
45 };
46
47 struct dlg_data {
48 const char** tags; // vec
49 struct dlg_tag_func_pair* pairs; // vec
50 char* buffer;
51 size_t buffer_size;
52 };
53
54 static dlg_handler g_handler = dlg_default_output;
55 static void* g_data = NULL;
56
57 static void dlg_free_data(void* data);
58 static struct dlg_data* dlg_create_data(void);
59
60 // platform-specific
61 #if defined(__unix__) || defined(__unix) || defined(__linux__) || defined(__APPLE__) || defined(__MACH__)
62 #define DLG_OS_UNIX
63 #include <unistd.h>
64 #include <pthread.h>
65 #include <sys/time.h>
66
67 static pthread_key_t dlg_data_key;
68
69 static void dlg_main_cleanup(void) {
70 void* data = pthread_getspecific(dlg_data_key);
71 if(data) {
72 dlg_free_data(data);
73 pthread_setspecific(dlg_data_key, NULL);
74 }
75 }
76
77 static void init_data_key(void) {
78 pthread_key_create(&dlg_data_key, dlg_free_data);
79 atexit(dlg_main_cleanup);
80 }
81
82 static struct dlg_data* dlg_data(void) {
83 static pthread_once_t key_once = PTHREAD_ONCE_INIT;
84 pthread_once(&key_once, init_data_key);
85
86 void* data = pthread_getspecific(dlg_data_key);
87 if(!data) {
88 data = dlg_create_data();
89 pthread_setspecific(dlg_data_key, data);
90 }
91
92 return (struct dlg_data*) data;
93 }
94
95 static void lock_file(FILE* file) {
96 flockfile(file);
97 }
98
99 static void unlock_file(FILE* file) {
100 funlockfile(file);
101 }
102
103 bool dlg_is_tty(FILE* stream) {
104 return isatty(fileno(stream));
105 }
106
107 static unsigned get_msecs(void) {
108 struct timeval tv;
109 gettimeofday(&tv, NULL);
110 return tv.tv_usec;
111 }
112
113 // platform switch -- end unix
114 #elif defined(WIN32) || defined(_WIN32) || defined(_WIN64)
115 #define DLG_OS_WIN
116 #define WIN32_LEAN_AND_MEAN
117 #define DEFINE_CONSOLEV2_PROPERTIES
118 #include <windows.h>
119 #include <io.h>
120
121 // thanks for nothing, microsoft
122 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
123 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
124 #endif
125
126 // the max buffer size we will convert on the stack
127 #define DLG_MAX_STACK_BUF_SIZE 1024
128
129 static void WINAPI dlg_fls_destructor(void* data) {
130 dlg_free_data(data);
131 }
132
133 // TODO: error handling
134 static BOOL CALLBACK dlg_init_fls(PINIT_ONCE io, void* param, void** lpContext) {
135 (void) io;
136 (void) param;
137 **((DWORD**) lpContext) = FlsAlloc(dlg_fls_destructor);
138 return true;
139 }
140
141 static struct dlg_data* dlg_data(void) {
142 static INIT_ONCE init_once = INIT_ONCE_STATIC_INIT;
143 static DWORD fls = 0;
144 void* flsp = (void*) &fls;
145 InitOnceExecuteOnce(&init_once, dlg_init_fls, NULL, &flsp);
146 void* data = FlsGetValue(fls);
147 if(!data) {
148 data = dlg_create_data();
149 FlsSetValue(fls, data);
150 }
151
152 return (struct dlg_data*) data;
153 }
154
155 static void lock_file(FILE* file) {
156 _lock_file(file);
157 }
158
159 static void unlock_file(FILE* file) {
160 _unlock_file(file);
161 }
162
163 bool dlg_is_tty(FILE* stream) {
164 return _isatty(_fileno(stream));
165 }
166
167 #ifdef DLG_WIN_CONSOLE
168 static bool init_ansi_console(void) {
169 HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
170 HANDLE err = GetStdHandle(STD_ERROR_HANDLE);
171 if(out == INVALID_HANDLE_VALUE || err == INVALID_HANDLE_VALUE)
172 return false;
173
174 DWORD outMode, errMode;
175 if(!GetConsoleMode(out, &outMode) || !GetConsoleMode(err, &errMode))
176 return false;
177
178 outMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
179 errMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
180 if(!SetConsoleMode(out, outMode) || !SetConsoleMode(out, errMode))
181 return false;
182
183 return true;
184 }
185
186 static bool win_write_heap(void* handle, int needed, const char* format, va_list args) {
187 char* buf1 = xalloc(3 * needed + 3 + (needed % 2));
188 wchar_t* buf2 = (wchar_t*) (buf1 + needed + 1 + (needed % 2));
189 vsnprintf(buf1, needed + 1, format, args);
190 needed = MultiByteToWideChar(CP_UTF8, 0, buf1, needed, buf2, needed + 1);
191 bool ret = (needed != 0 && WriteConsoleW(handle, buf2, needed, NULL, NULL) != 0);
192 free(buf1);
193 return ret;
194 }
195
196 static bool win_write_stack(void* handle, int needed, const char* format, va_list args) {
197 char buf1[DLG_MAX_STACK_BUF_SIZE];
198 wchar_t buf2[DLG_MAX_STACK_BUF_SIZE];
199 vsnprintf(buf1, needed + 1, format, args);
200 needed = MultiByteToWideChar(CP_UTF8, 0, buf1, needed, buf2, needed + 1);
201 return (needed != 0 && WriteConsoleW(handle, buf2, needed, NULL, NULL) != 0);
202 }
203 #endif // DLG_WIN_CONSOLE
204
205 static unsigned get_msecs() {
206 SYSTEMTIME st;
207 GetSystemTime(&st);
208 return st.wMilliseconds;
209 }
210
211 #else // platform switch -- end windows
212 #error Cannot determine platform (needed for color and utf-8 and stuff)
213 #endif
214
215 // general
216 void dlg_escape_sequence(struct dlg_style style, char buf[12]) {
217 int nums[3];
218 unsigned int count = 0;
219
220 if(style.fg != dlg_color_none) {
221 nums[count++] = style.fg + 30;
222 }
223
224 if(style.bg != dlg_color_none) {
225 nums[count++] = style.fg + 40;
226 }
227
228 if(style.style != dlg_text_style_none) {
229 nums[count++] = style.style;
230 }
231
232 switch(count) {
233 case 1: snprintf(buf, 12, "\033[%dm", nums[0]); break;
234 case 2: snprintf(buf, 12, "\033[%d;%dm", nums[0], nums[1]); break;
235 case 3: snprintf(buf, 12, "\033[%d;%d;%dm", nums[0], nums[1], nums[2]); break;
236 default: buf[0] = '\0'; break;
237 }
238 }
239
240 int dlg_vfprintf(FILE* stream, const char* format, va_list args) {
241 #if defined(DLG_OS_WIN) && defined(DLG_WIN_CONSOLE)
242 void* handle = NULL;
243 if(stream == stdout) {
244 handle = GetStdHandle(STD_OUTPUT_HANDLE);
245 } else if(stream == stderr) {
246 handle = GetStdHandle(STD_ERROR_HANDLE);
247 }
248
249 if(handle) {
250 va_list args_copy;
251 va_copy(args_copy, args);
252 int needed = vsnprintf(NULL, 0, format, args_copy);
253 va_end(args_copy);
254
255 if(needed < 0) {
256 return needed;
257 }
258
259 // We don't allocate too much on the stack
260 // but we also don't want to call alloc every logging call
261 // or use another cached buffer
262 if(needed >= DLG_MAX_STACK_BUF_SIZE) {
263 if(win_write_heap(handle, needed, format, args)) {
264 return needed;
265 }
266 } else {
267 if(win_write_stack(handle, needed, format, args)) {
268 return needed;
269 }
270 }
271 }
272 #endif
273
274 return vfprintf(stream, format, args);
275 }
276
277 int dlg_fprintf(FILE* stream, const char* format, ...) {
278 va_list args;
279 va_start(args, format);
280 int ret = dlg_vfprintf(stream, format, args);
281 va_end(args);
282 return ret;
283 }
284
285 int dlg_styled_fprintf(FILE* stream, struct dlg_style style, const char* format, ...) {
286 char buf[12];
287 dlg_escape_sequence(style, buf);
288
289 fprintf(stream, "%s", buf);
290 va_list args;
291 va_start(args, format);
292 int ret = dlg_vfprintf(stream, format, args);
293 va_end(args);
294 fprintf(stream, "%s", dlg_reset_sequence);
295 return ret;
296 }
297
298 void dlg_generic_output(dlg_generic_output_handler output, void* data,
299 unsigned int features, const struct dlg_origin* origin, const char* string,
300 const struct dlg_style styles[6]) {
301 // We never print any dynamic content below so we can be sure at compile
302 // time that a buffer of size 64 is large enough.
303 char format_buf[64];
304 char* format = format_buf;
305
306 if(features & dlg_output_style) {
307 format += sprintf(format, "%%s");
308 }
309
310 if(features & (dlg_output_time | dlg_output_file_line | dlg_output_tags | dlg_output_func)) {
311 format += sprintf(format, "[");
312 }
313
314 bool first_meta = true;
315 if(features & dlg_output_time) {
316 format += sprintf(format, "%%h");
317 first_meta = false;
318 }
319
320 if(features & dlg_output_time_msecs) {
321 if(!first_meta) {
322 format += sprintf(format, ":");
323 }
324
325 format += sprintf(format, "%%m");
326 first_meta = false;
327 }
328
329 if(features & dlg_output_file_line) {
330 if(!first_meta) {
331 format += sprintf(format, " ");
332 }
333
334 format += sprintf(format, "%%o");
335 first_meta = false;
336 }
337
338 if(features & dlg_output_func) {
339 if(!first_meta) {
340 format += sprintf(format, " ");
341 }
342
343 format += sprintf(format, "%%f");
344 first_meta = false;
345 }
346
347 if(features & dlg_output_tags) {
348 if(!first_meta) {
349 format += sprintf(format, " ");
350 }
351
352 format += sprintf(format, "{%%t}");
353 first_meta = false;
354 }
355
356 if(features & (dlg_output_time | dlg_output_file_line | dlg_output_tags | dlg_output_func)) {
357 format += sprintf(format, "] ");
358 }
359
360 format += sprintf(format, "%%c");
361
362 if(features & dlg_output_newline) {
363 format += sprintf(format, "\n");
364 }
365
366 *format = '\0';
367 dlg_generic_outputf(output, data, format_buf, origin, string, styles);
368 }
369
370 void dlg_generic_outputf(dlg_generic_output_handler output, void* data,
371 const char* format_string, const struct dlg_origin* origin, const char* string,
372 const struct dlg_style styles[6]) {
373 bool reset_style = false;
374 for(const char* it = format_string; *it; it++) {
375 if(*it != '%') {
376 output(data, "%c", *it);
377 continue;
378 }
379
380 char next = *(it + 1); // must be valid since *it is not '\0'
381 if(next == 'h') {
382 time_t t = time(NULL);
383 struct tm tm_info;
384
385 #ifdef DLG_OS_WIN
386 if(localtime_s(&tm_info, &t)) {
387 #else
388 if(!localtime_r(&t, &tm_info)) {
389 #endif
390 output(data, "<DATE ERROR>");
391 } else {
392 char timebuf[32];
393 strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm_info);
394 output(data, "%s", timebuf);
395 }
396 it++;
397 } else if(next == 'm') {
398 output(data, "%06d", get_msecs());
399 it++;
400 } else if(next == 't') {
401 bool first_tag = true;
402 for(const char** tags = origin->tags; *tags; ++tags) {
403 if(!first_tag) {
404 output(data, ", ");
405 }
406
407 output(data, "%s", *tags);
408 first_tag = false;
409 }
410 ++it;
411 } else if(next == 'f') {
412 output(data, "%s", origin->func);
413 ++it;
414 } else if(next == 'o') {
415 output(data, "%s:%u", origin->file, origin->line);
416 ++it;
417 } else if(next == 's') {
418 char buf[12];
419 dlg_escape_sequence(styles[origin->level], buf);
420 output(data, "%s", buf);
421 reset_style = true;
422 ++it;
423 } else if(next == 'r') {
424 output(data, "%s", dlg_reset_sequence);
425 reset_style = false;
426 ++it;
427 } else if(next == 'c') {
428 if(origin->expr && string) {
429 output(data, "assertion '%s' failed: '%s'", origin->expr, string);
430 } else if(origin->expr) {
431 output(data, "assertion '%s' failed", origin->expr);
432 } else if(string) {
433 output(data, "%s", string);
434 }
435 ++it;
436 } else if(next == '%') {
437 output(data, "%s", "%");
438 ++it;
439 } else {
440 // in this case it's a '%' without known format specifier following
441 output(data, "%s", "%");
442 }
443 }
444
445 if(reset_style) {
446 output(data, "%s", dlg_reset_sequence);
447 }
448 }
449
450 struct buf {
451 char* buf;
452 size_t* size;
453 };
454
455 static void print_size(void* size, const char* format, ...) {
456 va_list args;
457 va_start(args, format);
458
459 int ret = vsnprintf(NULL, 0, format, args);
460 va_end(args);
461
462 if(ret > 0) {
463 *((size_t*) size) += ret;
464 }
465 }
466
467 static void print_buf(void* dbuf, const char* format, ...) {
468 struct buf* buf = (struct buf*) dbuf;
469 va_list args;
470 va_start(args, format);
471
472 int printed = vsnprintf(buf->buf, *buf->size, format, args);
473 va_end(args);
474
475 if(printed > 0) {
476 *buf->size -= printed;
477 buf->buf += printed;
478 }
479 }
480
481 void dlg_generic_output_buf(char* buf, size_t* size, unsigned int features,
482 const struct dlg_origin* origin, const char* string,
483 const struct dlg_style styles[6]) {
484 if(buf) {
485 struct buf mbuf;
486 mbuf.buf = buf;
487 mbuf.size = size;
488 dlg_generic_output(print_buf, &mbuf, features, origin, string, styles);
489 } else {
490 *size = 0;
491 dlg_generic_output(print_size, size, features, origin, string, styles);
492 }
493 }
494
495 void dlg_generic_outputf_buf(char* buf, size_t* size, const char* format_string,
496 const struct dlg_origin* origin, const char* string,
497 const struct dlg_style styles[6]) {
498 if(buf) {
499 struct buf mbuf;
500 mbuf.buf = buf;
501 mbuf.size = size;
502 dlg_generic_outputf(print_buf, &mbuf, format_string, origin, string, styles);
503 } else {
504 *size = 0;
505 dlg_generic_outputf(print_size, size, format_string, origin, string, styles);
506 }
507 }
508
509 static void print_stream(void* stream, const char* format, ...) {
510 va_list args;
511 va_start(args, format);
512 dlg_vfprintf((FILE*) stream, format, args);
513 va_end(args);
514 }
515
516 void dlg_generic_output_stream(FILE* stream, unsigned int features,
517 const struct dlg_origin* origin, const char* string,
518 const struct dlg_style styles[6]) {
519 stream = stream ? stream : stdout;
520 if(features & dlg_output_threadsafe) {
521 lock_file(stream);
522 }
523
524 dlg_generic_output(print_stream, stream, features, origin, string, styles);
525 if(features & dlg_output_threadsafe) {
526 unlock_file(stream);
527 }
528 }
529
530 void dlg_generic_outputf_stream(FILE* stream, const char* format_string,
531 const struct dlg_origin* origin, const char* string,
532 const struct dlg_style styles[6], bool lock_stream) {
533 stream = stream ? stream : stdout;
534 if(lock_stream) {
535 lock_file(stream);
536 }
537
538 dlg_generic_outputf(print_stream, stream, format_string, origin, string, styles);
539 if(lock_stream) {
540 unlock_file(stream);
541 }
542 }
543
544 void dlg_default_output(const struct dlg_origin* origin, const char* string, void* data) {
545 FILE* stream = data ? (FILE*) data : stdout;
546 unsigned int features = dlg_output_file_line |
547 dlg_output_newline |
548 dlg_output_threadsafe;
549
550 #ifdef DLG_DEFAULT_OUTPUT_ALWAYS_COLOR
551 dlg_win_init_ansi();
552 features |= dlg_output_style;
553 #else
554 if(dlg_is_tty(stream) && dlg_win_init_ansi()) {
555 features |= dlg_output_style;
556 }
557 #endif
558
559 dlg_generic_output_stream(stream, features, origin, string, dlg_default_output_styles);
560 fflush(stream);
561 }
562
563 bool dlg_win_init_ansi(void) {
564 #if defined(DLG_OS_WIN) && defined(DLG_WIN_CONSOLE)
565 // TODO: use init once
566 static volatile LONG status = 0;
567 LONG res = InterlockedCompareExchange(&status, 1, 0);
568 if(res == 0) { // not initialized
569 InterlockedExchange(&status, 3 + init_ansi_console());
570 }
571
572 while(status == 1); // currently initialized in another thread, spinlock
573 return (status == 4);
574 #else
575 return true;
576 #endif
577 }
578
579 // small dynamic vec/array implementation
580 // Since the macros vec_init and vec_add[c]/vec_push might
581 // change the pointers value it must not be referenced somewhere else.
582 #define vec__raw(vec) (((unsigned int*) vec) - 2)
583
584 static void* vec_do_create(unsigned int typesize, unsigned int cap, unsigned int size) {
585 unsigned long a = (size > cap) ? size : cap;
586 void* ptr = xalloc(2 * sizeof(unsigned int) + a * typesize);
587 unsigned int* begin = (unsigned int*) ptr;
588 begin[0] = size * typesize;
589 begin[1] = a * typesize;
590 return begin + 2;
591 }
592
593 // NOTE: can be more efficient if we are allowed to reorder vector
594 static void vec_do_erase(void* vec, unsigned int pos, unsigned int size) {
595 unsigned int* begin = vec__raw(vec);
596 begin[0] -= size;
597 char* buf = (char*) vec;
598 memcpy(buf + pos, buf + pos + size, size);
599 }
600
601 static void* vec_do_add(void** vec, unsigned int size) {
602 unsigned int* begin = vec__raw(*vec);
603 unsigned int needed = begin[0] + size;
604 if(needed >= begin[1]) {
605 void* ptr = xrealloc(begin, sizeof(unsigned int) * 2 + needed * 2);
606 begin = (unsigned int*) ptr;
607 begin[1] = needed * 2;
608 (*vec) = begin + 2;
609 }
610
611 void* ptr = ((char*) (*vec)) + begin[0];
612 begin[0] += size;
613 return ptr;
614 }
615
616 #define vec_create(type, size) (type*) vec_do_create(sizeof(type), size * 2, size)
617 #define vec_create_reserve(type, size, capacity) (type*) vec_do_create(sizeof(type), capcity, size)
618 #define vec_init(array, size) array = vec_do_create(sizeof(*array), size * 2, size)
619 #define vec_init_reserve(array, size, capacity) *((void**) &array) = vec_do_create(sizeof(*array), capacity, size)
620 #define vec_free(vec) (free((vec) ? vec__raw(vec) : NULL), vec = NULL)
621 #define vec_erase_range(vec, pos, count) vec_do_erase(vec, pos * sizeof(*vec), count * sizeof(*vec))
622 #define vec_erase(vec, pos) vec_do_erase(vec, pos * sizeof(*vec), sizeof(*vec))
623 #define vec_size(vec) (vec__raw(vec)[0] / sizeof(*vec))
624 #define vec_capacity(vec) (vec_raw(vec)[1] / sizeof(*vec))
625 #define vec_add(vec) vec_do_add((void**) &vec, sizeof(*vec))
626 #define vec_addc(vec, count) (vec_do_add((void**) &vec, sizeof(*vec) * count))
627 #define vec_push(vec, value) (vec_do_add((void**) &vec, sizeof(*vec)), vec_last(vec) = (value))
628 #define vec_pop(vec) (vec__raw(vec)[0] -= sizeof(*vec))
629 #define vec_popc(vec, count) (vec__raw(vec)[0] -= sizeof(*vec) * count)
630 #define vec_clear(vec) (vec__raw(vec)[0] = 0)
631 #define vec_last(vec) (vec[vec_size(vec) - 1])
632
633 static struct dlg_data* dlg_create_data(void) {
634 struct dlg_data* data = (struct dlg_data*) xalloc(sizeof(struct dlg_data));
635 vec_init_reserve(data->tags, 0, 20);
636 vec_init_reserve(data->pairs, 0, 20);
637 data->buffer_size = 100;
638 data->buffer = (char*) xalloc(data->buffer_size);
639 return data;
640 }
641
642 static void dlg_free_data(void* ddata) {
643 struct dlg_data* data = (struct dlg_data*) ddata;
644 if(data) {
645 vec_free(data->pairs);
646 vec_free(data->tags);
647 free(data->buffer);
648 free(data);
649 }
650 }
651
652 void dlg_add_tag(const char* tag, const char* func) {
653 struct dlg_data* data = dlg_data();
654 struct dlg_tag_func_pair* pair =
655 (struct dlg_tag_func_pair*) vec_add(data->pairs);
656 pair->tag = tag;
657 pair->func = func;
658 }
659
660 bool dlg_remove_tag(const char* tag, const char* func) {
661 struct dlg_data* data = dlg_data();
662 for(unsigned int i = 0; i < vec_size(data->pairs); ++i) {
663 if(data->pairs[i].func == func && data->pairs[i].tag == tag) {
664 vec_erase(data->pairs, i);
665 return true;
666 }
667 }
668
669 return false;
670 }
671
672 char** dlg_thread_buffer(size_t** size) {
673 struct dlg_data* data = dlg_data();
674 if(size) {
675 *size = &data->buffer_size;
676 }
677 return &data->buffer;
678 }
679
680 void dlg_set_handler(dlg_handler handler, void* data) {
681 g_handler = handler;
682 g_data = data;
683 }
684
685 dlg_handler dlg_get_handler(void** data) {
686 *data = g_data;
687 return g_handler;
688 }
689
690 const char* dlg__printf_format(const char* str, ...) {
691 va_list vlist;
692 va_start(vlist, str);
693
694 va_list vlistcopy;
695 va_copy(vlistcopy, vlist);
696 int needed = vsnprintf(NULL, 0, str, vlist);
697 if(needed < 0) {
698 printf("dlg__printf_format: invalid format given\n");
699 va_end(vlist);
700 va_end(vlistcopy);
701 return NULL;
702 }
703
704 va_end(vlist);
705
706 size_t* buf_size;
707 char** buf = dlg_thread_buffer(&buf_size);
708 if(*buf_size <= (unsigned int) needed) {
709 *buf_size = (needed + 1) * 2;
710 *buf = (char*) xrealloc(*buf, *buf_size);
711 }
712
713 vsnprintf(*buf, *buf_size, str, vlistcopy);
714 va_end(vlistcopy);
715
716 return *buf;
717 }
718
719 void dlg__do_log(enum dlg_level lvl, const char* const* tags, const char* file, int line,
720 const char* func, const char* string, const char* expr) {
721 struct dlg_data* data = dlg_data();
722 unsigned int tag_count = 0;
723
724 // push default tags
725 while(tags[tag_count]) {
726 vec_push(data->tags, tags[tag_count++]);
727 }
728
729 // push current global tags
730 for(size_t i = 0; i < vec_size(data->pairs); ++i) {
731 const struct dlg_tag_func_pair pair = data->pairs[i];
732 if(pair.func == NULL || !strcmp(pair.func, func)) {
733 vec_push(data->tags, pair.tag);
734 }
735 }
736
737 // push call-specific tags, skip first terminating NULL
738 ++tag_count;
739 while(tags[tag_count]) {
740 vec_push(data->tags, tags[tag_count++]);
741 }
742
743 vec_push(data->tags, NULL); // terminating NULL
744 struct dlg_origin origin;
745 origin.level = lvl;
746 origin.file = file;
747 origin.line = line;
748 origin.func = func;
749 origin.expr = expr;
750 origin.tags = data->tags;
751
752 g_handler(&origin, string, g_data);
753 vec_clear(data->tags);
754 }
755
756 #ifdef _MSC_VER
757 // shitty msvc compatbility
758 // meson gives us sane paths (separated by '/') while on MSVC,
759 // __FILE__ contains a '\\' separator.
760 static bool path_same(char a, char b) {
761 return (a == b) ||
762 (a == '/' && b == '\\') ||
763 (a == '\\' && b == '/');
764 }
765 #else
766
767 static inline bool path_same(char a, char b) {
768 return a == b;
769 }
770
771 #endif
772
773 const char* dlg__strip_root_path(const char* file, const char* base) {
774 if(!file) {
775 return NULL;
776 }
777
778 const char* saved = file;
779 if(*file == '.') { // relative path detected
780 while(*(++file) == '.' || *file == '/' || *file == '\\');
781 if(*file == '\0') { // weird case: purely relative path without file
782 return saved;
783 }
784
785 return file;
786 }
787
788 // strip base from file if it is given
789 if(base) {
790 char fn = *file;
791 char bn = *base;
792 while(bn != '\0' && path_same(fn, bn)) {
793 fn = *(++file);
794 bn = *(++base);
795 }
796
797 if(fn == '\0' || bn != '\0') { // weird case: base isn't prefix of file
798 return saved;
799 }
800 }
801
802 return file;
803 }