diff options
Diffstat (limited to 'src/world.c')
-rw-r--r-- | src/world.c | 210 |
1 files changed, 190 insertions, 20 deletions
diff --git a/src/world.c b/src/world.c index d166b1d3..a6e32dc6 100644 --- a/src/world.c +++ b/src/world.c @@ -21,6 +21,11 @@ #include "cursor.h" #include "namespaces.h" #include "node.h" +#include "serd_config.h" + +#if USE_FILENO && USE_ISATTY +# include <unistd.h> +#endif #include <errno.h> #include <stdarg.h> @@ -30,32 +35,199 @@ #define BLANK_CHARS 12 +static inline int +level_color(const SerdLogLevel level) +{ + switch (level) { + case SERD_LOG_LEVEL_EMERG: + case SERD_LOG_LEVEL_ALERT: + case SERD_LOG_LEVEL_CRIT: + case SERD_LOG_LEVEL_ERR: + return 31; // Red + case SERD_LOG_LEVEL_WARNING: + return 33; // Yellow + case SERD_LOG_LEVEL_NOTICE: + case SERD_LOG_LEVEL_INFO: + case SERD_LOG_LEVEL_DEBUG: + break; + } + + return 1; // White +} + +static inline bool +terminal_supports_color(void) +{ + const char* const term = getenv("TERM"); + return !getenv("NO_COLOR") && term && strstr(term, "color"); +} + +static inline bool +serd_ansi_start(FILE* stream, int color, bool bold) +{ +#if USE_FILENO && USE_ISATTY + if (isatty(fileno(stream)) && terminal_supports_color()) { + return fprintf(stream, bold ? "\033[0;%d;1m" : "\033[0;%dm", color); + } +#endif + + return 0; +} + +static inline void +serd_ansi_reset(FILE* stream) +{ +#if USE_FILENO && USE_ISATTY + if (isatty(fileno(stream)) && terminal_supports_color()) { + fprintf(stream, "\033[0m"); + fflush(stream); + } +#endif +} + +static const char* const log_level_strings[] = {"emergengy", + "alert", + "critical", + "error", + "warning", + "note", + "info", + "debug"}; + +SERD_CONST_FUNC +SerdStatus +serd_quiet_error_func(void* handle, const SerdLogEntry* entry) +{ + (void)handle; + (void)entry; + return SERD_SUCCESS; +} + +static const char* +get_log_field(const SerdLogField* const fields, + const size_t n_fields, + const char* const key) +{ + for (size_t i = 0; i < n_fields; ++i) { + if (!strcmp(fields[i].key, key)) { + return fields[i].value; + } + } + + return NULL; +} + +const char* +serd_log_entry_get_field(const SerdLogEntry* const entry, const char* const key) +{ + return get_log_field(entry->fields, entry->n_fields, key); +} + SerdStatus -serd_world_error(const SerdWorld* world, const SerdError* e) +serd_world_vlogf(const SerdWorld* world, + const char* domain, + SerdLogLevel level, + const size_t n_fields, + const SerdLogField* fields, + const char* fmt, + va_list args) { - if (world->error_func) { - world->error_func(world->error_handle, e); + // Copy args (which may be an array) to portably get a pointer + va_list ap; + va_copy(ap, args); + + const SerdLogEntry e = {domain, fields, fmt, &ap, level, n_fields}; + SerdStatus st = SERD_SUCCESS; + + if (world->log_func) { + st = world->log_func(world->log_handle, &e); } else { - fprintf(stderr, "error: "); - if (e->cursor) { - fprintf(stderr, - "%s:%u:%u: ", - serd_node_string(e->cursor->file), - e->cursor->line, - e->cursor->col); + // Print input file and position prefix if available + const char* const file = serd_log_entry_get_field(&e, "SERD_FILE"); + const char* const line = serd_log_entry_get_field(&e, "SERD_LINE"); + const char* const col = serd_log_entry_get_field(&e, "SERD_COL"); + if (file && line && col) { + serd_ansi_start(stderr, 1, true); + fprintf(stderr, "%s:%s:%s: ", file, line, col); + serd_ansi_reset(stderr); } - vfprintf(stderr, e->fmt, *e->args); + + // Print GCC-style level prefix (error, warning, etc) + serd_ansi_start(stderr, level_color(level), true); + fprintf(stderr, "%s: ", log_level_strings[level]); + serd_ansi_reset(stderr); + + // Using a copy isn't necessary here, but it avoids a clang-tidy bug + vfprintf(stderr, fmt, ap); + } + + va_end(ap); + return st; +} + +SerdStatus +serd_world_logf(const SerdWorld* world, + const char* domain, + SerdLogLevel level, + const size_t n_fields, + const SerdLogField* fields, + const char* fmt, + ...) +{ + va_list args; + va_start(args, fmt); + + const SerdStatus st = + serd_world_vlogf(world, domain, level, n_fields, fields, fmt, args); + + va_end(args); + return st; +} + +SerdStatus +serd_world_vlogf_internal(const SerdWorld* world, + SerdStatus st, + SerdLogLevel level, + const SerdCursor* cursor, + const char* fmt, + va_list args) +{ + char st_str[12]; + snprintf(st_str, sizeof(st_str), "%u", st); + if (cursor) { + const char* file = serd_node_string(serd_cursor_name(cursor)); + + char line[24]; + snprintf(line, sizeof(line), "%u", serd_cursor_line(cursor)); + + char col[24]; + snprintf(col, sizeof(col), "%u", serd_cursor_column(cursor)); + + const SerdLogField fields[] = {{"SERD_STATUS", st_str}, + {"SERD_FILE", file}, + {"SERD_LINE", line}, + {"SERD_COL", col}}; + + serd_world_vlogf(world, "serd", level, 4, fields, fmt, args); + } else { + const SerdLogField fields[] = {{"SERD_STATUS", st_str}}; + serd_world_vlogf(world, "serd", level, 1, fields, fmt, args); } - return e->status; + + return st; } SerdStatus -serd_world_errorf(const SerdWorld* world, SerdStatus st, const char* fmt, ...) +serd_world_logf_internal(const SerdWorld* world, + SerdStatus st, + SerdLogLevel level, + const SerdCursor* cursor, + const char* fmt, + ...) { va_list args; va_start(args, fmt); - const SerdError e = {st, NULL, fmt, &args}; - serd_world_error(world, &e); + serd_world_vlogf_internal(world, st, level, cursor, fmt, args); va_end(args); return st; } @@ -118,10 +290,8 @@ serd_world_get_blank(SerdWorld* world) } void -serd_world_set_error_func(SerdWorld* world, - SerdErrorFunc error_func, - void* handle) +serd_world_set_log_func(SerdWorld* world, SerdLogFunc log_func, void* handle) { - world->error_func = error_func; - world->error_handle = handle; + world->log_func = log_func; + world->log_handle = handle; } |