aboutsummaryrefslogtreecommitdiffstats
path: root/src/world.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/world.c')
-rw-r--r--src/world.c210
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;
}