7#include <ascii-chat/log/colorize.h>
8#include <ascii-chat/log/logging.h>
9#include <ascii-chat/util/string.h>
10#include <ascii-chat/platform/system.h>
11#include <ascii-chat/platform/terminal.h>
12#include <ascii-chat/platform/util.h>
13#include <ascii-chat/video/ansi.h>
26static bool is_known_unit(
const char *str,
size_t max_len) {
27 if (!str || max_len == 0) {
32 const char *known_units[] = {
100 const size_t num_units =
sizeof(known_units) /
sizeof(known_units[0]);
101 for (
size_t i = 0; i < num_units; i++) {
102 size_t unit_len = strlen(known_units[i]);
103 if (unit_len > max_len) {
107 if (
platform_strncasecmp(str, known_units[i], unit_len) == 0 && (unit_len == max_len || !isalpha(str[unit_len]))) {
126static bool is_numeric_pattern(
const char *str,
size_t pos,
size_t *end_pos) {
127 if (!isdigit(str[pos]) && str[pos] !=
'.') {
132 if (pos > 0 && (isalnum(str[pos - 1]) || str[pos - 1] ==
'-' || str[pos - 1] ==
'_')) {
139 if (str[i] ==
'0' && (str[i + 1] ==
'x' || str[i + 1] ==
'X')) {
141 while (isxdigit(str[i])) {
149 bool has_digit =
false;
150 while (isdigit(str[i])) {
156 if (str[i] ==
'.' && isdigit(str[i + 1])) {
158 while (isdigit(str[i])) {
169 if (str[i] ==
'x' || str[i] ==
'X') {
170 if (isdigit(str[i + 1])) {
172 while (isdigit(str[i])) {
179 if (str[i] ==
'/' && isdigit(str[i + 1])) {
181 while (isdigit(str[i])) {
189 bool has_space =
false;
192 if ((isalpha(str[j]) || str[j] ==
'%') && (j == i || !isalnum(str[j - 1]))) {
193 size_t unit_start = j;
194 while ((isalpha(str[j]) || str[j] ==
'%') && (j - unit_start) < 32) {
197 size_t unit_len = j - unit_start;
200 if (is_known_unit(str + unit_start, unit_len)) {
211 while (str[j] ==
' ' || str[j] ==
'\t') {
216 if (has_space && (isalpha(str[j]) || str[j] ==
'%')) {
218 size_t unit_start = j;
219 while ((isalpha(str[j]) || str[j] ==
'%') && (j - unit_start) < 32) {
222 size_t unit_len = j - unit_start;
225 if (is_known_unit(str + unit_start, unit_len)) {
250static bool is_file_path(
const char *str,
size_t pos,
size_t *end_pos) {
255 if (pos > 0 && isalpha(str[pos]) && str[pos + 1] ==
':' && str[pos + 2] ==
'\\') {
260 else if (str[pos] ==
'\\' && str[pos + 1] ==
'\\') {
265 else if (str[pos] ==
'/') {
270 else if ((str[pos] ==
'.' && (str[pos + 1] ==
'/' || str[pos + 1] ==
'\\')) ||
271 (str[pos] ==
'.' && str[pos + 1] ==
'.' && (str[pos + 2] ==
'/' || str[pos + 2] ==
'\\'))) {
274 }
else if (isalnum(str[pos]) || str[pos] ==
'_' || str[pos] ==
'-') {
277 size_t lookahead = pos;
278 while ((isalnum(str[lookahead]) || str[lookahead] ==
'_' || str[lookahead] ==
'-' || str[lookahead] ==
'.' ||
279 str[lookahead] ==
'/' || str[lookahead] ==
'\\') &&
280 str[lookahead] !=
'\0') {
281 if (str[lookahead] ==
'/' || str[lookahead] ==
'\\') {
296 while (str[i] !=
'\0' && str[i] !=
' ' && str[i] !=
'\t' && str[i] !=
'\n' && str[i] !=
':' && str[i] !=
',' &&
297 str[i] !=
')' && str[i] !=
']' && str[i] !=
'}' && str[i] !=
'"' && str[i] !=
'\'') {
298 if ((str[i] ==
'/' || str[i] ==
'\\') || isalnum(str[i]) || strchr(
"._-~", str[i])) {
306 bool has_slash =
false;
307 for (
size_t j = pos; j < i; j++) {
308 if (str[j] ==
'/' || str[j] ==
'\\') {
332static bool is_url(
const char *str,
size_t pos,
size_t *end_pos) {
334 const char *schemes[] = {
"https://",
"http://",
"ftp://",
"wss://",
"ws://"};
337 for (
size_t s = 0; s <
sizeof(schemes) /
sizeof(schemes[0]); s++) {
338 size_t scheme_len = strlen(schemes[s]);
339 if (strncmp(str + pos, schemes[s], scheme_len) == 0) {
341 size_t i = pos + scheme_len;
345 while (str[i] !=
'\0' && str[i] !=
' ' && str[i] !=
'\t' && str[i] !=
'\n' && str[i] !=
')' && str[i] !=
']' &&
346 str[i] !=
'}' && str[i] !=
'"' && str[i] !=
'\'' && str[i] !=
'<' && str[i] !=
'>' && str[i] !=
',') {
351 if (i > pos + scheme_len) {
371static bool is_env_var(
const char *str,
size_t pos,
size_t *end_pos) {
372 if (str[pos] !=
'$') {
379 if (!isupper(str[i]) && str[i] !=
'_') {
383 while ((isupper(str[i]) || str[i] ==
'_' || isdigit(str[i])) && str[i] !=
'\0') {
402static log_color_t get_value_color(
const char *value) {
403 if (!value || *value ==
'\0') {
404 return LOG_COLOR_FATAL;
410 if (is_numeric_pattern(value, 0, &end_pos)) {
411 return LOG_COLOR_GREY;
414 if (is_url(value, 0, &end_pos)) {
415 return LOG_COLOR_INFO;
418 if (is_file_path(value, 0, &end_pos)) {
419 return LOG_COLOR_DEBUG;
422 if (is_env_var(value, 0, &end_pos)) {
423 return LOG_COLOR_GREY;
427 if ((value[0] ==
'"' || value[0] ==
'\'' || value[0] ==
'`') ||
428 strchr(value,
' ') != NULL) {
429 return LOG_COLOR_FATAL;
433 return LOG_COLOR_FATAL;
449static bool is_key_value_pair(
const char *str,
size_t pos,
size_t *key_end,
size_t *value_start,
size_t *value_end) {
451 if (!isalpha(str[pos]) && str[pos] !=
'_') {
456 if (pos > 0 && (isalnum(str[pos - 1]) || str[pos - 1] ==
'_')) {
463 while ((isalnum(str[i]) || str[i] ==
'_') && str[i] !=
'\0') {
467 size_t key_len = i - pos;
477 while (str[i] ==
' ' || str[i] ==
'\t') {
482 size_t val_start = i;
486 while (str[i] !=
'\0' && str[i] !=
' ' && str[i] !=
'\t' && str[i] !=
'\n' && str[i] !=
',' && str[i] !=
';' &&
487 str[i] !=
')' && str[i] !=
']' && str[i] !=
'}') {
491 size_t value_len = i - val_start;
494 if (value_len == 0) {
498 *key_end = pos + key_len;
499 *value_start = val_start;
522 if (!should_colorize) {
527 static char buffers[4][4096];
528 static int buffer_index = 0;
530 char *output = buffers[buffer_index];
531 buffer_index = (buffer_index + 1) % 4;
534 const size_t max_size =
sizeof(buffers[0]);
537 for (
size_t i = 0; message[i] !=
'\0' && out_pos < max_size - 100; i++) {
544 size_t key_end = 0, value_start = 0, value_end = 0;
545 if (can_colorize && is_key_value_pair(message, i, &key_end, &value_start, &value_end)) {
546 size_t key_len = key_end - i;
547 size_t value_len = value_end - value_start;
551 safe_snprintf(key_buf,
sizeof(key_buf),
"%.*s", (
int)key_len, message + i);
555 safe_snprintf(value_buf,
sizeof(value_buf),
"%.*s", (
int)value_len, message + value_start);
558 const char *colored_key =
colored_string(LOG_COLOR_FATAL, key_buf);
559 size_t colored_key_len = strlen(colored_key);
563 if (out_pos + colored_key_len + 1 + value_len + 100 < max_size) {
564 memcpy(output + out_pos, colored_key, colored_key_len);
565 out_pos += colored_key_len;
568 output[out_pos++] =
'=';
571 log_color_t value_color = get_value_color(value_buf);
572 const char *colored_value =
colored_string(value_color, value_buf);
573 size_t colored_value_len = strlen(colored_value);
574 if (out_pos + colored_value_len < max_size) {
575 memcpy(output + out_pos, colored_value, colored_value_len);
576 out_pos += colored_value_len;
584 if (can_colorize && is_numeric_pattern(message, i, &end_pos)) {
585 size_t pattern_len = end_pos - i;
586 char pattern_buf[256];
587 safe_snprintf(pattern_buf,
sizeof(pattern_buf),
"%.*s", (
int)pattern_len, message + i);
589 const char *colored =
colored_string(LOG_COLOR_GREY, pattern_buf);
590 size_t colored_len = strlen(colored);
591 if (out_pos + colored_len < max_size) {
592 memcpy(output + out_pos, colored, colored_len);
593 out_pos += colored_len;
600 if (can_colorize && is_file_path(message, i, &end_pos)) {
601 size_t path_len = end_pos - i;
603 safe_snprintf(path_buf,
sizeof(path_buf),
"%.*s", (
int)path_len, message + i);
606 size_t colored_len = strlen(colored);
607 if (out_pos + colored_len < max_size) {
608 memcpy(output + out_pos, colored, colored_len);
609 out_pos += colored_len;
616 if (can_colorize && is_env_var(message, i, &end_pos)) {
617 size_t var_len = end_pos - i;
619 safe_snprintf(var_buf,
sizeof(var_buf),
"%.*s", (
int)var_len, message + i);
622 size_t colored_len = strlen(colored);
623 if (out_pos + colored_len < max_size) {
624 memcpy(output + out_pos, colored, colored_len);
625 out_pos += colored_len;
632 if (can_colorize && is_url(message, i, &end_pos)) {
633 size_t url_len = end_pos - i;
635 safe_snprintf(url_buf,
sizeof(url_buf),
"%.*s", (
int)url_len, message + i);
638 size_t colored_len = strlen(colored);
639 if (out_pos + colored_len < max_size) {
640 memcpy(output + out_pos, colored, colored_len);
641 out_pos += colored_len;
648 if (out_pos < max_size - 1) {
649 output[out_pos++] = message[i];
653 output[out_pos] =
'\0';
bool ansi_is_already_colorized(const char *message, size_t pos)
const char * colorize_log_message(const char *message)
Colorize a log message for terminal output.
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
const char * colored_string(log_color_t color, const char *text)