aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.clang-tidy3
-rw-r--r--src/log.c325
-rw-r--r--src/log.h21
-rw-r--r--src/reader.c7
-rw-r--r--src/reader.h3
-rw-r--r--src/serd_config.h13
-rw-r--r--src/world.c66
-rw-r--r--src/world.h26
-rw-r--r--src/writer.c24
9 files changed, 392 insertions, 96 deletions
diff --git a/src/.clang-tidy b/src/.clang-tidy
index 77b43fc1..53834f98 100644
--- a/src/.clang-tidy
+++ b/src/.clang-tidy
@@ -1,11 +1,10 @@
-# Copyright 2020-2022 David Robillard <d@drobilla.net>
+# Copyright 2020-2023 David Robillard <d@drobilla.net>
# SPDX-License-Identifier: 0BSD OR ISC
Checks: >
-*-magic-numbers,
-bugprone-easily-swappable-parameters,
-cert-err33-c,
- -clang-analyzer-valist.Uninitialized,
-clang-diagnostic-unused-function,
-hicpp-multiway-paths-covered,
-hicpp-signed-bitwise,
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 00000000..6ba885c6
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,325 @@
+// Copyright 2011-2022 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#include "log.h"
+#include "serd_config.h"
+#include "world.h"
+
+#include "serd/attributes.h"
+#include "serd/caret.h"
+#include "serd/log.h"
+#include "serd/node.h"
+#include "serd/status.h"
+#include "serd/string_view.h"
+#include "serd/world.h"
+#include "zix/attributes.h"
+
+#if USE_ISATTY
+# include <unistd.h>
+#endif
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static const char* const log_level_strings[] = {"emergency",
+ "alert",
+ "critical",
+ "error",
+ "warning",
+ "note",
+ "info",
+ "debug"};
+
+static int
+level_color(const SerdLogLevel level)
+{
+ switch (level) {
+ case SERD_LOG_LEVEL_EMERGENCY:
+ case SERD_LOG_LEVEL_ALERT:
+ case SERD_LOG_LEVEL_CRITICAL:
+ case SERD_LOG_LEVEL_ERROR:
+ 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 void
+serd_ansi_start(const bool enabled,
+ FILE* const stream,
+ const int color,
+ const bool bold)
+{
+ if (enabled) {
+ fprintf(stream, bold ? "\033[0;%d;1m" : "\033[0;%dm", color);
+ }
+}
+
+static void
+serd_ansi_reset(const bool enabled, FILE* const stream)
+{
+ if (enabled) {
+ fprintf(stream, "\033[0m");
+ fflush(stream);
+ }
+}
+
+static bool
+terminal_supports_color(const int fd)
+{
+ // https://no-color.org/
+ // NOLINTNEXTLINE(concurrency-mt-unsafe)
+ if (getenv("NO_COLOR")) {
+ return false;
+ }
+
+ // https://bixense.com/clicolors/
+ // NOLINTNEXTLINE(concurrency-mt-unsafe)
+ const char* const clicolor_force = getenv("CLICOLOR_FORCE");
+ if (clicolor_force && !!strcmp(clicolor_force, "0")) {
+ return true;
+ }
+
+ // https://bixense.com/clicolors/
+ // NOLINTNEXTLINE(concurrency-mt-unsafe)
+ const char* const clicolor = getenv("CLICOLOR");
+ if (clicolor && !strcmp(clicolor, "0")) {
+ return false;
+ }
+
+#if USE_ISATTY
+ // Assume support if stream is a TTY (blissfully ignoring termcap nightmares)
+ return isatty(fd);
+#else
+ (void)stream;
+ return false;
+#endif
+}
+
+SerdStatus
+serd_log_init(SerdLog* const log)
+{
+ log->func = NULL;
+ log->handle = NULL;
+ log->stderr_color = terminal_supports_color(1);
+ return SERD_SUCCESS;
+}
+
+SerdStatus
+serd_quiet_log_func(void* const handle,
+ const SerdLogLevel level,
+ const size_t n_fields,
+ const SerdLogField* const fields,
+ const SerdStringView message)
+{
+ (void)handle;
+ (void)level;
+ (void)n_fields;
+ (void)fields;
+ (void)message;
+ return SERD_SUCCESS;
+}
+
+static const char*
+get_log_field(const size_t n_fields,
+ const SerdLogField* const 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;
+}
+
+void
+serd_set_log_func(SerdWorld* const world,
+ const SerdLogFunc log_func,
+ void* const handle)
+{
+ assert(world);
+
+ world->log.func = log_func;
+ world->log.handle = handle;
+}
+
+SERD_LOG_FUNC(5, 0)
+static SerdStatus
+serd_default_vxlogf(const bool stderr_color,
+ const SerdLogLevel level,
+ const size_t n_fields,
+ const SerdLogField* const fields,
+ const char* const fmt,
+ va_list args)
+{
+ assert(fmt);
+
+ // Print input file and position prefix if available
+ const char* const file = get_log_field(n_fields, fields, "SERD_FILE");
+ const char* const line = get_log_field(n_fields, fields, "SERD_LINE");
+ const char* const col = get_log_field(n_fields, fields, "SERD_COL");
+ if (file) {
+ serd_ansi_start(stderr_color, stderr, 1, true);
+ if (line && col) {
+ fprintf(stderr, "%s:%s:%s: ", file, line, col);
+ } else {
+ fprintf(stderr, "%s: ", file);
+ }
+ serd_ansi_reset(stderr_color, stderr);
+ }
+
+ // Print GCC-style level prefix (error, warning, etc)
+ serd_ansi_start(stderr_color, stderr, level_color(level), true);
+ fprintf(stderr, "%s: ", log_level_strings[level]);
+ serd_ansi_reset(stderr_color, stderr);
+
+ // Format and print the message itself
+ vfprintf(stderr, fmt, args);
+
+ // Print clang-tidy style check suffix
+ const char* const check = get_log_field(n_fields, fields, "SERD_CHECK");
+ if (check) {
+ fprintf(stderr, " [%s]", check);
+ }
+
+ fprintf(stderr, "\n");
+ return SERD_SUCCESS;
+}
+
+SerdStatus
+serd_vxlogf(const SerdWorld* const world,
+ const SerdLogLevel level,
+ const size_t n_fields,
+ const SerdLogField* const fields,
+ const char* const fmt,
+ va_list args)
+{
+ assert(world);
+ assert(fmt);
+
+ if (world->log.func) {
+ char message[512] = {0};
+ const int r = vsnprintf(message, sizeof(message), fmt, args);
+
+ return (r <= 0 || (size_t)r >= sizeof(message))
+ ? SERD_BAD_ARG
+ : world->log.func(world->log.handle,
+ level,
+ n_fields,
+ fields,
+ serd_substring(message, (size_t)r));
+ }
+
+ return serd_default_vxlogf(
+ world->log.stderr_color, level, n_fields, fields, fmt, args);
+}
+
+SerdStatus
+serd_xlogf(const SerdWorld* const world,
+ const SerdLogLevel level,
+ const size_t n_fields,
+ const SerdLogField* const fields,
+ const char* const fmt,
+ ...)
+{
+ assert(world);
+ assert(fmt);
+
+ va_list args; // NOLINT(cppcoreguidelines-init-variables)
+ va_start(args, fmt);
+
+ const SerdStatus st = serd_vxlogf(world, level, n_fields, fields, fmt, args);
+
+ va_end(args);
+ return st;
+}
+
+SerdStatus
+serd_vlogf(const SerdWorld* const world,
+ const SerdLogLevel level,
+ const char* const fmt,
+ va_list args)
+{
+ assert(world);
+ assert(fmt);
+
+ return serd_vxlogf(world, level, 0U, NULL, fmt, args);
+}
+
+SerdStatus
+serd_logf(const SerdWorld* const world,
+ const SerdLogLevel level,
+ const char* const fmt,
+ ...)
+{
+ assert(world);
+ assert(fmt);
+
+ va_list args; // NOLINT(cppcoreguidelines-init-variables)
+ va_start(args, fmt);
+
+ const SerdStatus st = serd_vxlogf(world, level, 0U, NULL, fmt, args);
+
+ va_end(args);
+ return st;
+}
+
+SerdStatus
+serd_vlogf_at(const SerdWorld* ZIX_NONNULL world,
+ SerdLogLevel level,
+ const SerdCaret* caret,
+ const char* ZIX_NONNULL fmt,
+ va_list args)
+{
+ assert(world);
+ assert(fmt);
+
+ const SerdNode* const document = caret ? serd_caret_document(caret) : NULL;
+ if (!document) {
+ return serd_vxlogf(world, level, 0U, NULL, fmt, args);
+ }
+
+ char line[24];
+ char col[24];
+ snprintf(line, sizeof(line), "%u", serd_caret_line(caret));
+ snprintf(col, sizeof(col), "%u", serd_caret_column(caret));
+
+ const SerdLogField fields[] = {
+ {"SERD_FILE", serd_node_string(document)},
+ {"SERD_LINE", line},
+ {"SERD_COL", col},
+ };
+
+ return serd_vxlogf(world, level, 3, fields, fmt, args);
+}
+
+SerdStatus
+serd_logf_at(const SerdWorld* ZIX_NONNULL world,
+ SerdLogLevel level,
+ const SerdCaret* caret,
+ const char* ZIX_NONNULL fmt,
+ ...)
+{
+ assert(world);
+ assert(fmt);
+
+ va_list args; // NOLINT(cppcoreguidelines-init-variables)
+ va_start(args, fmt);
+
+ const SerdStatus st = serd_vlogf_at(world, level, caret, fmt, args);
+
+ va_end(args);
+ return st;
+}
diff --git a/src/log.h b/src/log.h
new file mode 100644
index 00000000..ebb7dd60
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,21 @@
+// Copyright 2011-2023 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#ifndef SERD_SRC_LOG_H
+#define SERD_SRC_LOG_H
+
+#include "serd/log.h"
+#include "serd/status.h"
+
+#include <stdbool.h>
+
+typedef struct {
+ SerdLogFunc func;
+ void* handle;
+ bool stderr_color;
+} SerdLog;
+
+SerdStatus
+serd_log_init(SerdLog* log);
+
+#endif // SERD_SRC_LOG_H
diff --git a/src/reader.c b/src/reader.c
index 6f5ccce6..f34f7974 100644
--- a/src/reader.c
+++ b/src/reader.c
@@ -17,6 +17,7 @@
#include "world.h"
#include "serd/input_stream.h"
+#include "serd/log.h"
#include <assert.h>
#include <stdarg.h>
@@ -32,8 +33,10 @@ r_err(SerdReader* const reader, const SerdStatus st, const char* const fmt, ...)
{
va_list args; // NOLINT(cppcoreguidelines-init-variables)
va_start(args, fmt);
- const SerdError e = {st, &reader->source->caret, fmt, &args};
- serd_world_error(reader->world, &e);
+
+ serd_vlogf_at(
+ reader->world, SERD_LOG_LEVEL_ERROR, &reader->source->caret, fmt, args);
+
va_end(args);
return st;
}
diff --git a/src/reader.h b/src/reader.h
index 01ad5890..601453b0 100644
--- a/src/reader.h
+++ b/src/reader.h
@@ -10,7 +10,6 @@
#include "try.h"
#include "serd/attributes.h"
-#include "serd/error.h"
#include "serd/node.h"
#include "serd/reader.h"
#include "serd/sink.h"
@@ -36,8 +35,6 @@ typedef struct {
struct SerdReaderImpl {
SerdWorld* world;
const SerdSink* sink;
- SerdLogFunc error_func;
- void* error_handle;
SerdNode* rdf_first;
SerdNode* rdf_rest;
SerdNode* rdf_nil;
diff --git a/src/serd_config.h b/src/serd_config.h
index 90c4d31e..0bd9b5ac 100644
--- a/src/serd_config.h
+++ b/src/serd_config.h
@@ -60,6 +60,13 @@
# endif
# endif
+// POSIX.1-2001: isatty()
+# ifndef HAVE_ISATTY
+# if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L
+# define HAVE_ISATTY
+# endif
+# endif
+
// POSIX.1-2001: posix_fadvise()
# ifndef HAVE_POSIX_FADVISE
# if SERD__POSIX_VERSION >= 200112L && !defined(__APPLE__)
@@ -97,6 +104,12 @@
# define USE_FILENO 0
#endif
+#if defined(HAVE_ISATTY) && HAVE_ISATTY
+# define USE_ISATTY 1
+#else
+# define USE_ISATTY 0
+#endif
+
#if defined(HAVE_POSIX_FADVISE) && HAVE_POSIX_FADVISE
# define USE_POSIX_FADVISE 1
#else
diff --git a/src/world.c b/src/world.c
index 9f10de49..f5ab2f19 100644
--- a/src/world.c
+++ b/src/world.c
@@ -3,68 +3,19 @@
#include "world.h"
-#include "caret.h"
+#include "log.h"
#include "node.h"
#include "serd/node.h"
-
+#include "serd/status.h"
#include "serd/string_view.h"
+#include "serd/world.h"
#include <assert.h>
-#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-SerdStatus
-serd_world_error(const SerdWorld* const world, const SerdError* const e)
-{
- if (world->error_func) {
- world->error_func(world->error_handle, e);
- } else {
- fprintf(stderr, "error: ");
- if (e->caret) {
- fprintf(stderr,
- "%s:%u:%u: ",
- serd_node_string(e->caret->document),
- e->caret->line,
- e->caret->col);
- }
- vfprintf(stderr, e->fmt, *e->args);
- fprintf(stderr, "\n");
- }
- return e->status;
-}
-
-SerdStatus
-serd_world_verrorf(const SerdWorld* const world,
- const SerdStatus st,
- const char* const fmt,
- va_list args)
-{
- va_list args_copy;
- va_copy(args_copy, args);
-
- const SerdError e = {st, NULL, fmt, &args_copy};
- serd_world_error(world, &e);
- va_end(args_copy);
- return st;
-}
-
-SerdStatus
-serd_world_errorf(const SerdWorld* const world,
- const SerdStatus st,
- const char* const fmt,
- ...)
-{
- va_list args; // NOLINT(cppcoreguidelines-init-variables)
- va_start(args, fmt);
- const SerdError e = {st, NULL, fmt, &args};
- serd_world_error(world, &e);
- va_end(args);
- return st;
-}
-
SerdWorld*
serd_world_new(void)
{
@@ -81,6 +32,8 @@ serd_world_new(void)
world->limits.writer_max_depth = 128U;
world->blank_node = blank_node;
+ serd_log_init(&world->log);
+
return world;
}
@@ -125,12 +78,3 @@ serd_world_get_blank(SerdWorld* const world)
#undef BLANK_CHARS
}
-
-void
-serd_world_set_error_func(SerdWorld* world,
- SerdLogFunc error_func,
- void* handle)
-{
- world->error_func = error_func;
- world->error_handle = handle;
-}
diff --git a/src/world.h b/src/world.h
index 56db46cc..af6281d4 100644
--- a/src/world.h
+++ b/src/world.h
@@ -4,32 +4,18 @@
#ifndef SERD_SRC_WORLD_H
#define SERD_SRC_WORLD_H
-#include "serd/error.h"
+#include "log.h"
+
#include "serd/node.h"
-#include "serd/status.h"
#include "serd/world.h"
-#include <stdarg.h>
#include <stdint.h>
struct SerdWorldImpl {
- SerdLimits limits;
- SerdLogFunc error_func;
- void* error_handle;
- uint32_t next_blank_id;
- SerdNode* blank_node;
+ SerdLimits limits;
+ SerdLog log;
+ uint32_t next_blank_id;
+ SerdNode* blank_node;
};
-SerdStatus
-serd_world_error(const SerdWorld* world, const SerdError* e);
-
-SerdStatus
-serd_world_errorf(const SerdWorld* world, SerdStatus st, const char* fmt, ...);
-
-SerdStatus
-serd_world_verrorf(const SerdWorld* world,
- SerdStatus st,
- const char* fmt,
- va_list args);
-
#endif // SERD_SRC_WORLD_H
diff --git a/src/writer.c b/src/writer.c
index 940b22a0..94c75625 100644
--- a/src/writer.c
+++ b/src/writer.c
@@ -16,6 +16,7 @@
#include "serd/attributes.h"
#include "serd/env.h"
#include "serd/event.h"
+#include "serd/log.h"
#include "serd/node.h"
#include "serd/output_stream.h"
#include "serd/sink.h"
@@ -161,7 +162,7 @@ write_node(SerdWriter* writer,
SerdField field,
SerdStatementFlags flags);
-SERD_NODISCARD static bool
+static bool
supports_abbrev(const SerdWriter* writer)
{
return writer->syntax == SERD_TURTLE || writer->syntax == SERD_TRIG;
@@ -198,7 +199,7 @@ w_err(SerdWriter* writer, SerdStatus st, const char* fmt, ...)
va_list args; // NOLINT(cppcoreguidelines-init-variables)
va_start(args, fmt);
- serd_world_verrorf(writer->world, st, fmt, args);
+ serd_vlogf(writer->world, SERD_LOG_LEVEL_ERROR, fmt, args);
va_end(args);
return st;
@@ -264,9 +265,13 @@ sink(const void* buf, size_t len, SerdWriter* writer)
char message[1024] = {0};
serd_system_strerror(errno, message, sizeof(message));
- w_err(writer, SERD_BAD_WRITE, "write error (%s)\n", message);
+ w_err(writer, SERD_BAD_WRITE, "write error (%s)", message);
} else {
- w_err(writer, SERD_BAD_WRITE, "write error\n");
+ w_err(writer,
+ SERD_BAD_WRITE,
+ "unknown write error, %zu / %zu bytes written",
+ written,
+ len);
}
}
@@ -291,7 +296,7 @@ write_character(SerdWriter* const writer,
const uint32_t c = parse_utf8_char(utf8, size);
switch (*size) {
case 0:
- *st = w_err(writer, SERD_BAD_TEXT, "invalid UTF-8 start: %X\n", utf8[0]);
+ *st = w_err(writer, SERD_BAD_TEXT, "invalid UTF-8 start: %X", utf8[0]);
return 0;
case 1:
snprintf(escape, sizeof(escape), "\\u%04X", utf8[0]);
@@ -864,7 +869,7 @@ write_uri_node(SerdWriter* const writer,
!serd_env_base_uri(writer->env)) {
return w_err(writer,
SERD_BAD_ARG,
- "syntax does not support URI reference <%s>\n",
+ "URI reference <%s> in unsupported syntax",
node_str);
}
@@ -886,7 +891,7 @@ write_curie(SerdWriter* const writer, const SerdNode* const node)
if (!supports_abbrev(writer) || !fast) {
const SerdStringView curie = serd_node_string_view(node);
if ((st = serd_env_expand_in_place(writer->env, curie, &prefix, &suffix))) {
- return w_err(writer, st, "undefined namespace prefix '%s'\n", node_str);
+ return w_err(writer, st, "undefined namespace prefix '%s'", node_str);
}
}
@@ -1307,7 +1312,10 @@ serd_writer_end_anon(SerdWriter* writer, const SerdNode* node)
}
if (!writer->anon_stack_size) {
- return w_err(writer, SERD_BAD_EVENT, "unexpected end of anonymous node\n");
+ return w_err(writer,
+ SERD_BAD_EVENT,
+ "unexpected end of anonymous node '%s'",
+ serd_node_string(node));
}
// Write the end separator ']' and pop the context