aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--NEWS1
-rw-r--r--include/serd/serd.h216
-rw-r--r--meson.build2
-rw-r--r--src/log.c240
-rw-r--r--src/n3.c113
-rw-r--r--src/reader.c9
-rw-r--r--src/reader.h10
-rw-r--r--src/serd_config.h13
-rw-r--r--src/serdi.c10
-rw-r--r--src/world.c76
-rw-r--r--src/world.h13
-rw-r--r--src/writer.c52
-rw-r--r--test/meson.build19
-rw-r--r--test/test_log.c128
14 files changed, 734 insertions, 168 deletions
diff --git a/NEWS b/NEWS
index ddc64095..4ac154de 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,7 @@ serd (1.0.1) unstable;
* Add SerdBuffer for mutable buffers to keep SerdChunk const-correct
* Add SerdWorld for shared library state
+ * Add extensible logging API
* Add option for writing terse output without newlines
* Add support for parsing variables
* Add support for writing terse collections
diff --git a/include/serd/serd.h b/include/serd/serd.h
index b0892573..403e637c 100644
--- a/include/serd/serd.h
+++ b/include/serd/serd.h
@@ -66,6 +66,14 @@
SERD_API \
SERD_MALLOC_FUNC
+#if defined(__MINGW32__)
+# define SERD_LOG_FUNC(fmt, a) __attribute__((format(gnu_printf, fmt, a)))
+#elif defined(__GNUC__)
+# define SERD_LOG_FUNC(fmt, a) __attribute__((format(printf, fmt, a)))
+#else
+# define SERD_LOG_FUNC(fmt, a)
+#endif
+
#ifdef __cplusplus
extern "C" {
# if defined(__GNUC__)
@@ -1183,23 +1191,6 @@ serd_statement_equals(const SerdStatement* SERD_NULLABLE a,
/// Global library state
typedef struct SerdWorldImpl SerdWorld;
-/// An error description
-typedef struct {
- SerdStatus status; ///< Error code
- const SerdCaret* SERD_NULLABLE caret; ///< File origin of error
- const char* SERD_NONNULL fmt; ///< Printf-style format string
- va_list* SERD_NONNULL args; ///< Arguments for fmt
-} SerdError;
-
-/**
- Callback function for errors.
-
- @param handle Handle for user data.
- @param error Error description.
-*/
-typedef SerdStatus (*SerdErrorFunc)(void* SERD_NULLABLE handle,
- const SerdError* SERD_NONNULL error);
-
/**
Create a new Serd World.
@@ -1237,16 +1228,195 @@ const SerdNode* SERD_NONNULL
serd_world_get_blank(SerdWorld* SERD_NONNULL world);
/**
- Set a function to be called when errors occur.
+ @}
+ @defgroup serd_logging Logging
+ @{
+*/
+
+/// Log entry level, compatible with syslog
+typedef enum {
+ SERD_LOG_LEVEL_EMERGENCY, ///< Emergency, system is unusable
+ SERD_LOG_LEVEL_ALERT, ///< Action must be taken immediately
+ SERD_LOG_LEVEL_CRITICAL, ///< Critical condition
+ SERD_LOG_LEVEL_ERROR, ///< Error
+ SERD_LOG_LEVEL_WARNING, ///< Warning
+ SERD_LOG_LEVEL_NOTICE, ///< Normal but significant condition
+ SERD_LOG_LEVEL_INFO, ///< Informational message
+ SERD_LOG_LEVEL_DEBUG ///< Debug message
+} SerdLogLevel;
+
+/**
+ A structured log field.
+
+ Fields are used to add metadata to log messages. Syslog-compatible keys
+ should be used where possible, otherwise, keys should be namespaced to
+ prevent clashes.
+
+ Serd itself uses the following keys:
- The `error_func` will be called with `handle` as its first argument. If
- no error function is set, errors are printed to stderr.
+ - ERRNO - The `errno` of the original system error if any (decimal string)
+ - SERD_COL - The 1-based column number in the file (decimal string)
+ - SERD_FILE - The file which caused this message (string)
+ - SERD_LINE - The 1-based line number in the file (decimal string)
+*/
+typedef struct {
+ const char* SERD_NONNULL key; ///< Field name
+ const char* SERD_NONNULL value; ///< Field value
+} SerdLogField;
+
+/**
+ Function for handling log messages.
+
+ By default, the log is printed to `stderr`. This can be overridden by
+ passing a function of this type to serd_set_log_func().
+
+ @param handle Pointer to opaque user data.
+ @param level Log level.
+ @param n_fields Number of entries in `fields`.
+ @param fields An array of `n_fields` extra log fields.
+ @param message Log message.
+*/
+typedef SerdStatus (*SerdLogFunc)(void* SERD_NULLABLE handle,
+ SerdLogLevel level,
+ size_t n_fields,
+ const SerdLogField* SERD_NULLABLE fields,
+ SerdStringView message);
+
+/// A #SerdLogFunc that does nothing (for suppressing log output)
+SERD_CONST_API
+SerdStatus
+serd_quiet_log_func(void* SERD_NULLABLE handle,
+ SerdLogLevel level,
+ size_t n_fields,
+ const SerdLogField* SERD_NULLABLE fields,
+ SerdStringView message);
+
+/**
+ Set a function to be called with log messages (typically errors).
+
+ If no custom logging function is set, then messages are printed to stderr.
+
+ @param world World that will send log entries to the given function.
+
+ @param log_func Log function to call for every log message. Each call to
+ this function represents a complete log message with an implicit trailing
+ newline.
+
+ @param handle Opaque handle that will be passed to every invocation of
+ `log_func`.
*/
SERD_API
void
-serd_world_set_error_func(SerdWorld* SERD_NONNULL world,
- SerdErrorFunc SERD_NULLABLE error_func,
- void* SERD_NULLABLE handle);
+serd_set_log_func(SerdWorld* SERD_NONNULL world,
+ SerdLogFunc SERD_NULLABLE log_func,
+ void* SERD_NULLABLE handle);
+
+/**
+ Write a message to the log with a `va_list`.
+
+ This is the fundamental and most powerful function for writing entries to
+ the log, the others are convenience wrappers that ultimately call this.
+
+ This writes a single complete entry to the log, and so may not be used to
+ print parts of a line like a more general printf-like function. There
+ should be no trailing newline in `fmt`. Arguments following `fmt` should
+ correspond to conversion specifiers in the format string as in printf from
+ the standard C library.
+
+ @param world World to log to.
+ @param level Log level.
+ @param n_fields Number of entries in `fields`.
+ @param fields An array of `n_fields` extra log fields.
+ @param fmt Format string.
+ @param args Arguments for `fmt`.
+
+ @return A status code, which is always #SERD_SUCCESS with the default log
+ function. If a custom log function is set with serd_set_log_func() and it
+ returns an error, then that error is returned here.
+*/
+SERD_API
+SERD_LOG_FUNC(5, 0)
+SerdStatus
+serd_vxlogf(const SerdWorld* SERD_NONNULL world,
+ SerdLogLevel level,
+ size_t n_fields,
+ const SerdLogField* SERD_NULLABLE fields,
+ const char* SERD_NONNULL fmt,
+ va_list args);
+
+/**
+ Write a message to the log with extra fields.
+
+ This is a convenience wrapper for serd_vxlogf() that takes the format
+ arguments directly.
+*/
+SERD_API
+SERD_LOG_FUNC(5, 6)
+SerdStatus
+serd_xlogf(const SerdWorld* SERD_NONNULL world,
+ SerdLogLevel level,
+ size_t n_fields,
+ const SerdLogField* SERD_NULLABLE fields,
+ const char* SERD_NONNULL fmt,
+ ...);
+
+/**
+ Write a simple message to the log.
+
+ This is a convenience wrapper for serd_vxlogf() which sets no extra fields.
+*/
+SERD_API
+SERD_LOG_FUNC(3, 0)
+SerdStatus
+serd_vlogf(const SerdWorld* SERD_NONNULL world,
+ SerdLogLevel level,
+ const char* SERD_NONNULL fmt,
+ va_list args);
+
+/**
+ Write a simple message to the log.
+
+ This is a convenience wrapper for serd_vlogf() that takes the format
+ arguments directly.
+*/
+SERD_API
+SERD_LOG_FUNC(3, 4)
+SerdStatus
+serd_logf(const SerdWorld* SERD_NONNULL world,
+ SerdLogLevel level,
+ const char* SERD_NONNULL fmt,
+ ...);
+
+/**
+ Write a message to the log with a caret position.
+
+ This is a convenience wrapper for serd_vxlogf() which sets `SERD_FILE`,
+ `SERD_LINE`, and `SERD_COL` to the position of the given caret. Entries are
+ typically printed with a GCC-style prefix like "file.ttl:16:4".
+*/
+SERD_API
+SERD_LOG_FUNC(4, 0)
+SerdStatus
+serd_vlogf_at(const SerdWorld* SERD_NONNULL world,
+ SerdLogLevel level,
+ const SerdCaret* SERD_NULLABLE caret,
+ const char* SERD_NONNULL fmt,
+ va_list args);
+
+/**
+ Write a message to the log with a caret position.
+
+ This is a convenience wrapper for serd_vlogf_at() that takes the format
+ arguments directly.
+*/
+SERD_API
+SERD_LOG_FUNC(4, 5)
+SerdStatus
+serd_logf_at(const SerdWorld* SERD_NONNULL world,
+ SerdLogLevel level,
+ const SerdCaret* SERD_NULLABLE caret,
+ const char* SERD_NONNULL fmt,
+ ...);
/**
@}
diff --git a/meson.build b/meson.build
index cdc35ffc..3b6dc21e 100644
--- a/meson.build
+++ b/meson.build
@@ -53,6 +53,7 @@ if get_option('strict')
'/wd4706', # assignment within conditional expression
'/wd4710', # function not inlined
'/wd4711', # function selected for automatic inline expansion
+ '/wd4774', # format string is not a string literal
'/wd4800', # implicit conversion from int to bool
'/wd4820', # padding added after construct
'/wd4996', # POSIX name for this item is deprecated
@@ -86,6 +87,7 @@ sources = [
'src/byte_source.c',
'src/caret.c',
'src/env.c',
+ 'src/log.c',
'src/n3.c',
'src/node.c',
'src/nodes.c',
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 00000000..23db44f9
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,240 @@
+/*
+ Copyright 2011-2022 David Robillard <d@drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#include "world.h"
+
+#include "serd/serd.h"
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+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 const char* const log_level_strings[] = {"emergency",
+ "alert",
+ "critical",
+ "error",
+ "warning",
+ "note",
+ "info",
+ "debug"};
+
+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)
+{
+ world->log_func = log_func;
+ world->log_handle = handle;
+}
+
+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)
+{
+ 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_ERR_BAD_ARG
+ : world->log_func(world->log_handle,
+ level,
+ n_fields,
+ fields,
+ SERD_SUBSTRING(message, (size_t)r));
+ }
+
+ // 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(world->stderr_color, stderr, 1, true);
+ if (line && col) {
+ fprintf(stderr, "%s:%s:%s: ", file, line, col);
+ } else {
+ fprintf(stderr, "%s: ", file);
+ }
+ serd_ansi_reset(world->stderr_color, stderr);
+ }
+
+ // Print GCC-style level prefix (error, warning, etc)
+ serd_ansi_start(world->stderr_color, stderr, level_color(level), true);
+ fprintf(stderr, "%s: ", log_level_strings[level]);
+ serd_ansi_reset(world->stderr_color, stderr);
+
+ // Format and print the message itself
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, "\n");
+
+ return SERD_SUCCESS;
+}
+
+SerdStatus
+serd_xlogf(const SerdWorld* const world,
+ const SerdLogLevel level,
+ const size_t n_fields,
+ const SerdLogField* const fields,
+ const char* const fmt,
+ ...)
+{
+ va_list args;
+ 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)
+{
+ return serd_vxlogf(world, level, 0u, NULL, fmt, args);
+}
+
+SerdStatus
+serd_logf(const SerdWorld* const world,
+ const SerdLogLevel level,
+ const char* const fmt,
+ ...)
+{
+ va_list args;
+ 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* SERD_NONNULL world,
+ SerdLogLevel level,
+ const SerdCaret* caret,
+ const char* SERD_NONNULL fmt,
+ va_list args)
+{
+ if (!caret) {
+ 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(serd_caret_name(caret))},
+ {"SERD_LINE", line},
+ {"SERD_COL", col},
+ };
+
+ return serd_vxlogf(world, level, 3, fields, fmt, args);
+}
+
+SerdStatus
+serd_logf_at(const SerdWorld* SERD_NONNULL world,
+ SerdLogLevel level,
+ const SerdCaret* caret,
+ const char* SERD_NONNULL fmt,
+ ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ const SerdStatus st = serd_vlogf_at(world, level, caret, fmt, args);
+
+ va_end(args);
+ return st;
+}
diff --git a/src/n3.c b/src/n3.c
index 14f5aec1..c91303d9 100644
--- a/src/n3.c
+++ b/src/n3.c
@@ -62,7 +62,7 @@ read_HEX(SerdReader* const reader)
return (uint8_t)eat_byte_safe(reader, c);
}
- r_err(reader, SERD_ERR_BAD_SYNTAX, "invalid hexadecimal digit `%c'\n", c);
+ r_err(reader, SERD_ERR_BAD_SYNTAX, "invalid hexadecimal digit `%c'", c);
return 0;
}
@@ -107,10 +107,9 @@ read_UCHAR(SerdReader* const reader,
} else if (code < 0x00110000) {
size = 4;
} else {
- r_err(reader,
- SERD_ERR_BAD_SYNTAX,
- "unicode character 0x%X out of range\n",
- code);
+ r_err(
+ reader, SERD_ERR_BAD_SYNTAX, "unicode character 0x%X out of range", code);
+
*char_code = 0xFFFD;
const SerdStatus st = push_bytes(reader, dest, replacement_char, 3);
return st ? st : SERD_SUCCESS;
@@ -199,14 +198,14 @@ read_utf8_bytes(SerdReader* const reader,
{
*size = utf8_num_bytes(c);
if (*size <= 1 || *size > 4) {
- return bad_char(reader, "invalid UTF-8 start 0x%X\n", c);
+ return bad_char(reader, "invalid UTF-8 start 0x%X", c);
}
bytes[0] = c;
for (unsigned i = 1; i < *size; ++i) {
const int b = peek_byte(reader);
if (b == EOF || ((uint8_t)b & 0x80) == 0) {
- return bad_char(reader, "invalid UTF-8 continuation 0x%X\n", (uint8_t)b);
+ return bad_char(reader, "invalid UTF-8 continuation 0x%X", (uint8_t)b);
}
eat_byte_safe(reader, b);
@@ -355,10 +354,10 @@ read_STRING_LITERAL_LONG(SerdReader* const reader,
uint32_t code = 0;
if ((st = read_ECHAR(reader, ref)) &&
(st = read_UCHAR(reader, ref, &code))) {
- return r_err(reader, st, "invalid escape `\\%c'\n", peek_byte(reader));
+ return r_err(reader, st, "invalid escape `\\%c'", peek_byte(reader));
}
} else if (c == EOF) {
- st = r_err(reader, SERD_ERR_NO_DATA, "unexpected end of file\n");
+ st = r_err(reader, SERD_ERR_NO_DATA, "unexpected end of file");
} else if (c == q) {
eat_byte_safe(reader, q);
const int q2 = eat_byte_safe(reader, peek_byte(reader));
@@ -393,16 +392,15 @@ read_STRING_LITERAL(SerdReader* const reader,
uint32_t code = 0;
switch (c) {
case EOF:
- return r_err(
- reader, SERD_ERR_BAD_SYNTAX, "end of file in short string\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "end of file in short string");
case '\n':
case '\r':
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "line end in short string\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "line end in short string");
case '\\':
eat_byte_safe(reader, c);
if ((st = read_ECHAR(reader, ref)) &&
(st = read_UCHAR(reader, ref, &code))) {
- return r_err(reader, st, "invalid escape `\\%c'\n", peek_byte(reader));
+ return r_err(reader, st, "invalid escape `\\%c'", peek_byte(reader));
}
break;
default:
@@ -426,7 +424,7 @@ read_String(SerdReader* const reader, SerdNode* const node)
const int q2 = peek_byte(reader);
if (q2 == EOF) {
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "unexpected end of file\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "unexpected end of file");
}
if (q2 != q1) { // Short string (not triple quoted)
@@ -436,7 +434,7 @@ read_String(SerdReader* const reader, SerdNode* const node)
eat_byte_safe(reader, q2);
const int q3 = peek_byte(reader);
if (q3 == EOF) {
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "unexpected end of file\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "unexpected end of file");
}
if (q3 != q1) { // Empty short string ("" or '')
@@ -445,7 +443,7 @@ read_String(SerdReader* const reader, SerdNode* const node)
if (!fancy_syntax(reader)) {
return r_err(
- reader, SERD_ERR_BAD_SYNTAX, "syntax does not support long literals\n");
+ reader, SERD_ERR_BAD_SYNTAX, "syntax does not support long literals");
}
eat_byte_safe(reader, q3);
@@ -478,7 +476,7 @@ read_PN_CHARS_BASE(SerdReader* const reader, SerdNode* const dest)
return st;
} else if (!is_PN_CHARS_BASE(code)) {
r_err(
- reader, SERD_ERR_BAD_SYNTAX, "invalid character U+%04X in name\n", code);
+ reader, SERD_ERR_BAD_SYNTAX, "invalid character U+%04X in name", code);
if (reader->strict) {
return SERD_ERR_BAD_SYNTAX;
}
@@ -508,7 +506,7 @@ read_PN_CHARS(SerdReader* const reader, SerdNode* const dest)
return st;
} else if (!is_PN_CHARS(code)) {
return r_err(
- reader, SERD_ERR_BAD_SYNTAX, "invalid character U+%04X in name\n", code);
+ reader, SERD_ERR_BAD_SYNTAX, "invalid character U+%04X in name", code);
}
return st;
}
@@ -566,7 +564,7 @@ read_PN_LOCAL_ESC(SerdReader* const reader, SerdNode* const dest)
break;
}
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "invalid escape\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "invalid escape");
}
static SerdStatus
@@ -608,7 +606,7 @@ read_PN_LOCAL(SerdReader* const reader,
break;
default:
if ((st = read_PLX(reader, dest)) > SERD_FAILURE) {
- return r_err(reader, st, "bad escape\n");
+ return r_err(reader, st, "bad escape");
} else if (st != SERD_SUCCESS && read_PN_CHARS_BASE(reader, dest)) {
return SERD_FAILURE;
}
@@ -618,7 +616,7 @@ read_PN_LOCAL(SerdReader* const reader,
if (c == '.' || c == ':') {
st = push_byte(reader, dest, eat_byte_safe(reader, c));
} else if ((st = read_PLX(reader, dest)) > SERD_FAILURE) {
- return r_err(reader, st, "bad escape\n");
+ return r_err(reader, st, "bad escape");
} else if (st != SERD_SUCCESS && (st = read_PN_CHARS(reader, dest))) {
break;
}
@@ -654,7 +652,7 @@ read_PN_PREFIX_tail(SerdReader* const reader, SerdNode* const dest)
if ((st = read_PN_CHARS(reader, dest))) {
return r_err(reader,
st > SERD_FAILURE ? st : SERD_ERR_BAD_SYNTAX,
- "prefix ends with `.'\n");
+ "prefix ends with `.'");
}
}
@@ -678,7 +676,7 @@ read_LANGTAG(SerdReader* const reader)
{
int c = peek_byte(reader);
if (!is_alpha(c)) {
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "unexpected `%c'\n", c);
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "unexpected `%c'", c);
}
SerdNode* node = push_node(reader, SERD_LITERAL, "", 0);
@@ -705,19 +703,19 @@ read_IRIREF_scheme(SerdReader* const reader, SerdNode* const dest)
{
int c = peek_byte(reader);
if (!is_alpha(c)) {
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "bad IRI scheme start `%c'\n", c);
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "bad IRI scheme start `%c'", c);
}
SerdStatus st = SERD_SUCCESS;
while ((c = peek_byte(reader)) != EOF) {
if (c == '>') {
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "missing IRI scheme\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "missing IRI scheme");
}
if (!is_uri_scheme_char(c)) {
return r_err(reader,
SERD_ERR_BAD_SYNTAX,
- "bad IRI scheme char U+%04X (%c)\n",
+ "bad IRI scheme char U+%04X (%c)",
(unsigned)c,
(char)c);
}
@@ -747,7 +745,7 @@ read_IRIREF(SerdReader* const reader, SerdNode** const dest)
}
if (!fancy_syntax(reader) && (st = read_IRIREF_scheme(reader, *dest))) {
- return r_err(reader, st, "expected IRI scheme\n");
+ return r_err(reader, st, "expected IRI scheme");
}
uint32_t code = 0;
@@ -757,12 +755,12 @@ read_IRIREF(SerdReader* const reader, SerdNode** const dest)
case '"':
case '<':
return r_err(
- reader, SERD_ERR_BAD_SYNTAX, "invalid IRI character `%c'\n", c);
+ reader, SERD_ERR_BAD_SYNTAX, "invalid IRI character `%c'", c);
case '>':
return SERD_SUCCESS;
case '\\':
if (read_UCHAR(reader, *dest, &code)) {
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "invalid IRI escape\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "invalid IRI escape");
}
switch (code) {
case 0:
@@ -771,7 +769,7 @@ read_IRIREF(SerdReader* const reader, SerdNode** const dest)
case '>':
return r_err(reader,
SERD_ERR_BAD_SYNTAX,
- "invalid escaped IRI character U+%04X\n",
+ "invalid escaped IRI character U+%04X",
code);
default:
break;
@@ -783,12 +781,12 @@ read_IRIREF(SerdReader* const reader, SerdNode** const dest)
case '|':
case '}':
return r_err(
- reader, SERD_ERR_BAD_SYNTAX, "invalid IRI character `%c'\n", c);
+ reader, SERD_ERR_BAD_SYNTAX, "invalid IRI character `%c'", c);
default:
if (c <= 0x20) {
st = r_err(reader,
SERD_ERR_BAD_SYNTAX,
- "invalid IRI character (escape %%%02X)\n",
+ "invalid IRI character (escape %%%02X)",
(unsigned)c);
if (reader->strict) {
break;
@@ -841,7 +839,7 @@ read_0_9(SerdReader* const reader, SerdNode* const str, const bool at_least_one)
}
if (at_least_one && count == 0) {
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "expected digit\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "expected digit");
}
return st;
@@ -987,7 +985,7 @@ read_Var(SerdReader* const reader, SerdNode** const dest)
{
if (!(reader->flags & SERD_READ_VARIABLES)) {
return r_err(
- reader, SERD_ERR_BAD_SYNTAX, "syntax does not support variables\n");
+ reader, SERD_ERR_BAD_SYNTAX, "syntax does not support variables");
}
if (!(*dest = push_node(reader, SERD_VARIABLE, "", 0))) {
@@ -1039,7 +1037,7 @@ read_verb(SerdReader* reader, SerdNode** dest)
if ((st = read_PrefixedName(reader, *dest, false, &ate_dot)) || ate_dot) {
*dest = NULL;
return r_err(
- reader, st > SERD_FAILURE ? st : SERD_ERR_BAD_SYNTAX, "expected verb\n");
+ reader, st > SERD_FAILURE ? st : SERD_ERR_BAD_SYNTAX, "expected verb");
}
return SERD_SUCCESS;
@@ -1067,7 +1065,7 @@ read_BLANK_NODE_LABEL(SerdReader* const reader,
if (is_digit(c) || c == '_') {
TRY(st, push_byte(reader, n, eat_byte_safe(reader, c)));
} else if ((st = read_PN_CHARS(reader, n))) {
- return r_err(reader, st, "invalid name start\n");
+ return r_err(reader, st, "invalid name start");
}
while ((c = peek_byte(reader))) { // Middle: (PN_CHARS | '.')*
@@ -1098,7 +1096,7 @@ read_BLANK_NODE_LABEL(SerdReader* const reader,
} else if (reader->seen_genid && buf[reader->bprefix_len] == 'B') {
return r_err(reader,
SERD_ERR_ID_CLASH,
- "found both `b' and `B' blank IDs, prefix required\n");
+ "found both `b' and `B' blank IDs, prefix required");
}
}
}
@@ -1142,7 +1140,7 @@ read_anon(SerdReader* const reader,
TRY(st, read_predicateObjectList(reader, ctx, &ate_dot_in_list));
if (ate_dot_in_list) {
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "`.' inside blank\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "`.' inside blank");
}
read_ws_star(reader);
@@ -1185,14 +1183,14 @@ read_object(SerdReader* const reader,
case '?':
break;
default:
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "expected: ':', '<', or '_'\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "expected: ':', '<', or '_'");
}
}
switch (c) {
case EOF:
case ')':
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "expected object\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "expected object");
case '$':
case '?':
ret = read_Var(reader, &o);
@@ -1260,7 +1258,7 @@ read_object(SerdReader* const reader,
} else if ((ret = read_PN_PREFIX_tail(reader, o)) > SERD_FAILURE ||
(ret = read_PrefixedName(reader, o, false, ate_dot))) {
ret = (ret > SERD_FAILURE) ? ret : SERD_ERR_BAD_SYNTAX;
- return r_err(reader, ret, "expected prefixed name\n");
+ return r_err(reader, ret, "expected prefixed name");
}
}
@@ -1285,7 +1283,7 @@ read_objectList(SerdReader* const reader, ReadContext ctx, bool* const ate_dot)
TRY(st, read_object(reader, &ctx, true, ate_dot));
if (!fancy_syntax(reader) && peek_delim(reader, ',')) {
return r_err(
- reader, SERD_ERR_BAD_SYNTAX, "syntax does not support abbreviation\n");
+ reader, SERD_ERR_BAD_SYNTAX, "syntax does not support abbreviation");
}
while (st <= SERD_FAILURE && !*ate_dot && eat_delim(reader, ',')) {
@@ -1317,7 +1315,7 @@ read_predicateObjectList(SerdReader* const reader,
switch (c = peek_byte(reader)) {
case EOF:
serd_stack_pop_to(&reader->stack, orig_stack_size);
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "unexpected end of file\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "unexpected end of file");
case '.':
case ']':
case '}':
@@ -1331,7 +1329,7 @@ read_predicateObjectList(SerdReader* const reader,
if (!ate_semi) {
serd_stack_pop_to(&reader->stack, orig_stack_size);
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "missing ';' or '.'\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "missing ';' or '.'");
}
}
@@ -1443,7 +1441,7 @@ read_subject(SerdReader* const reader,
}
if (ate_dot) {
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "subject ends with `.'\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "subject ends with `.'");
}
return st;
@@ -1470,7 +1468,7 @@ read_labelOrSubject(SerdReader* const reader, SerdNode** const dest)
if (!read_iri(reader, dest, &ate_dot)) {
return SERD_SUCCESS;
} else {
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "expected label or subject\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "expected label or subject");
}
}
}
@@ -1521,7 +1519,7 @@ read_base(SerdReader* const reader, const bool sparql, const bool token)
}
if (peek_byte(reader) == '.') {
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "full stop after SPARQL BASE\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "full stop after SPARQL BASE");
}
return SERD_SUCCESS;
@@ -1577,7 +1575,7 @@ read_directive(SerdReader* const reader)
switch (peek_byte(reader)) {
case 'B':
case 'P':
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "uppercase directive\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "uppercase directive");
}
}
@@ -1592,7 +1590,7 @@ read_directive(SerdReader* const reader)
break;
}
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "invalid directive\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "invalid directive");
}
static SerdStatus
@@ -1611,12 +1609,12 @@ read_wrappedGraph(SerdReader* const reader, ReadContext* const ctx)
ctx->subject = 0;
if ((st = read_subject(reader, *ctx, &ctx->subject, &s_type))) {
- return r_err(reader, st, "expected subject\n");
+ return r_err(reader, st, "expected subject");
}
if (read_triples(reader, *ctx, &ate_dot) && s_type != '[') {
return r_err(
- reader, SERD_ERR_BAD_SYNTAX, "missing predicate object list\n");
+ reader, SERD_ERR_BAD_SYNTAX, "missing predicate object list");
}
serd_stack_pop_to(&reader->stack, orig_stack_size);
@@ -1630,7 +1628,7 @@ read_wrappedGraph(SerdReader* const reader, ReadContext* const ctx)
eat_byte_safe(reader, '}');
read_ws_star(reader);
if (peek_byte(reader) == '.') {
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "graph followed by `.'\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "graph followed by `.'");
}
return SERD_SUCCESS;
@@ -1662,7 +1660,7 @@ read_n3_statement(SerdReader* const reader)
case '@':
if (!fancy_syntax(reader)) {
return r_err(
- reader, SERD_ERR_BAD_SYNTAX, "syntax does not support directives\n");
+ reader, SERD_ERR_BAD_SYNTAX, "syntax does not support directives");
}
TRY(st, read_directive(reader));
read_ws_star(reader);
@@ -1673,7 +1671,7 @@ read_n3_statement(SerdReader* const reader)
read_ws_star(reader);
} else {
return r_err(
- reader, SERD_ERR_BAD_SYNTAX, "syntax does not support graphs\n");
+ reader, SERD_ERR_BAD_SYNTAX, "syntax does not support graphs");
}
break;
default:
@@ -1695,7 +1693,7 @@ read_n3_statement(SerdReader* const reader)
read_ws_star(reader);
} else if (read_ws_star(reader) && peek_byte(reader) == '{') {
if (s_type == '(' || (s_type == '[' && !*ctx.flags)) {
- return r_err(reader, SERD_ERR_BAD_SYNTAX, "invalid graph name\n");
+ return r_err(reader, SERD_ERR_BAD_SYNTAX, "invalid graph name");
}
ctx.graph = ctx.subject;
@@ -1709,7 +1707,7 @@ read_n3_statement(SerdReader* const reader)
if (ate_dot && (reader->strict || (s_type != '('))) {
return r_err(
- reader, SERD_ERR_BAD_SYNTAX, "unexpected end of statement\n");
+ reader, SERD_ERR_BAD_SYNTAX, "unexpected end of statement");
}
return st > SERD_FAILURE ? st : SERD_ERR_BAD_SYNTAX;
@@ -1768,8 +1766,7 @@ read_nquadsDoc(SerdReader* const reader)
}
if (peek_byte(reader) == '@') {
- r_err(
- reader, SERD_ERR_BAD_SYNTAX, "syntax does not support directives\n");
+ r_err(reader, SERD_ERR_BAD_SYNTAX, "syntax does not support directives");
return SERD_ERR_BAD_SYNTAX;
}
diff --git a/src/reader.c b/src/reader.c
index d70cbb53..c2962bdb 100644
--- a/src/reader.c
+++ b/src/reader.c
@@ -22,7 +22,6 @@
#include "stack.h"
#include "statement.h"
#include "system.h"
-#include "world.h"
#include <stdarg.h>
#include <stdio.h>
@@ -37,8 +36,10 @@ r_err(SerdReader* const reader, const SerdStatus st, const char* const fmt, ...)
{
va_list args;
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;
}
@@ -253,7 +254,7 @@ skip_bom(SerdReader* const me)
serd_byte_source_advance(me->source) ||
serd_byte_source_peek(me->source) != 0xBF ||
serd_byte_source_advance(me->source)) {
- r_err(me, SERD_ERR_BAD_SYNTAX, "corrupt byte order mark\n");
+ r_err(me, SERD_ERR_BAD_SYNTAX, "corrupt byte order mark");
return SERD_ERR_BAD_SYNTAX;
}
}
diff --git a/src/reader.h b/src/reader.h
index b08c3fd0..cc8656d3 100644
--- a/src/reader.h
+++ b/src/reader.h
@@ -28,12 +28,6 @@
#include <stdint.h>
#include <stdio.h>
-#if defined(__GNUC__)
-# define SERD_LOG_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1)))
-#else
-# define SERD_LOG_FUNC(fmt, arg1)
-#endif
-
typedef struct {
SerdNode* graph;
SerdNode* subject;
@@ -45,7 +39,7 @@ typedef struct {
struct SerdReaderImpl {
SerdWorld* world;
const SerdSink* sink;
- SerdErrorFunc error_func;
+ SerdLogFunc log_func;
void* error_handle;
SerdNode* rdf_first;
SerdNode* rdf_rest;
@@ -131,7 +125,7 @@ eat_byte_check(SerdReader* reader, const int byte)
const int c = peek_byte(reader);
if (c != byte) {
return r_err(
- reader, SERD_ERR_BAD_SYNTAX, "expected `%c', not `%c'\n", byte, c);
+ reader, SERD_ERR_BAD_SYNTAX, "expected `%c', not `%c'", byte, c);
}
eat_byte_safe(reader, byte);
diff --git a/src/serd_config.h b/src/serd_config.h
index d5a6db33..53378a48 100644
--- a/src/serd_config.h
+++ b/src/serd_config.h
@@ -51,6 +51,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
# ifndef __APPLE__
@@ -90,6 +97,12 @@
# define USE_FILENO 0
#endif
+#ifdef HAVE_ISATTY
+# define USE_ISATTY 1
+#else
+# define USE_ISATTY 0
+#endif
+
#ifdef HAVE_POSIX_FADVISE
# define USE_POSIX_FADVISE 1
#else
diff --git a/src/serdi.c b/src/serdi.c
index 9a5495af..28cf870d 100644
--- a/src/serdi.c
+++ b/src/serdi.c
@@ -86,14 +86,6 @@ missing_arg(const char* const name, const char opt)
}
static SerdStatus
-quiet_error_func(void* const handle, const SerdError* const e)
-{
- (void)handle;
- (void)e;
- return SERD_SUCCESS;
-}
-
-static SerdStatus
read_file(SerdWorld* const world,
SerdSyntax syntax,
const SerdReaderFlags flags,
@@ -333,7 +325,7 @@ main(int argc, char** argv)
serd_writer_new(world, output_syntax, writer_flags, env, byte_sink);
if (quiet) {
- serd_world_set_error_func(world, quiet_error_func, NULL);
+ serd_set_log_func(world, serd_quiet_log_func, NULL);
}
if (root_uri) {
diff --git a/src/world.c b/src/world.c
index bd70d615..d0ad60da 100644
--- a/src/world.c
+++ b/src/world.c
@@ -16,48 +16,53 @@
#include "world.h"
-#include "caret.h"
#include "namespaces.h"
#include "node.h"
+#include "serd_config.h"
-#include <stdarg.h>
+#if USE_FILENO && USE_ISATTY
+# include <unistd.h>
+#endif
+
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BLANK_CHARS 12
-SerdStatus
-serd_world_error(const SerdWorld* const world, const SerdError* const e)
+static bool
+terminal_supports_color(FILE* const stream)
{
- 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->file),
- e->caret->line,
- e->caret->col);
- }
- vfprintf(stderr, e->fmt, *e->args);
+ // https://no-color.org/
+ // NOLINTNEXTLINE(concurrency-mt-unsafe)
+ if (getenv("NO_COLOR")) {
+ return false;
}
- return e->status;
-}
-SerdStatus
-serd_world_errorf(const SerdWorld* const world,
- const SerdStatus st,
- const char* const fmt,
- ...)
-{
- va_list args;
- va_start(args, fmt);
- const SerdError e = {st, NULL, fmt, &args};
- serd_world_error(world, &e);
- va_end(args);
- return st;
+ // 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_FILENO && USE_ISATTY
+
+ // Assume support if stream is a TTY (blissfully ignoring termcap nightmares)
+ return isatty(fileno(stream));
+
+#else
+ (void)stream;
+ return false;
+#endif
}
SerdWorld*
@@ -85,6 +90,8 @@ serd_world_new(void)
world->blank_node = serd_new_blank(SERD_STRING("b00000000000"));
world->nodes = nodes;
+ world->stderr_color = terminal_supports_color(stderr);
+
return world;
}
@@ -115,12 +122,3 @@ serd_world_get_blank(SerdWorld* const world)
return world->blank_node;
}
-
-void
-serd_world_set_error_func(SerdWorld* world,
- SerdErrorFunc error_func,
- void* handle)
-{
- world->error_func = error_func;
- world->error_handle = handle;
-}
diff --git a/src/world.h b/src/world.h
index a70a6e28..8043663e 100644
--- a/src/world.h
+++ b/src/world.h
@@ -19,12 +19,13 @@
#include "serd/serd.h"
+#include <stdbool.h>
#include <stdint.h>
struct SerdWorldImpl {
SerdNodes* nodes;
- SerdErrorFunc error_func;
- void* error_handle;
+ SerdLogFunc log_func;
+ void* log_handle;
SerdNode* blank_node;
const SerdNode* rdf_first;
const SerdNode* rdf_nil;
@@ -34,12 +35,8 @@ struct SerdWorldImpl {
const SerdNode* xsd_decimal;
const SerdNode* xsd_integer;
uint32_t next_blank_id;
-};
-
-SerdStatus
-serd_world_error(const SerdWorld* world, const SerdError* e);
-SerdStatus
-serd_world_errorf(const SerdWorld* world, SerdStatus st, const char* fmt, ...);
+ bool stderr_color;
+};
#endif // SERD_WORLD_H
diff --git a/src/writer.c b/src/writer.c
index 1ad62a18..3d4cad19 100644
--- a/src/writer.c
+++ b/src/writer.c
@@ -28,6 +28,7 @@
#include <assert.h>
#include <errno.h>
+#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
@@ -146,6 +147,19 @@ write_node(SerdWriter* writer,
SerdField field,
SerdStatementFlags flags);
+SERD_LOG_FUNC(3, 4)
+static SerdStatus
+w_err(SerdWriter* const writer, const SerdStatus st, const char* const fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ serd_vlogf(writer->world, SERD_LOG_LEVEL_ERROR, fmt, args);
+
+ va_end(args);
+ return st;
+}
+
static bool
supports_abbrev(const SerdWriter* writer)
{
@@ -242,10 +256,13 @@ sink(const void* buf, size_t len, SerdWriter* writer)
char message[1024] = {0};
serd_system_strerror(errno, message, sizeof(message));
- serd_world_errorf(
- writer->world, SERD_ERR_BAD_WRITE, "write error (%s)\n", message);
+ w_err(writer, SERD_ERR_BAD_WRITE, "write error (%s)", message);
} else {
- serd_world_errorf(writer->world, SERD_ERR_BAD_WRITE, "write error\n");
+ w_err(writer,
+ SERD_ERR_BAD_WRITE,
+ "unknown write error, %zu / %zu bytes written",
+ written,
+ len);
}
}
@@ -270,8 +287,7 @@ write_character(SerdWriter* writer,
const uint32_t c = parse_utf8_char(utf8, size);
switch (*size) {
case 0:
- serd_world_errorf(
- writer->world, SERD_ERR_BAD_ARG, "invalid UTF-8 start: %X\n", utf8[0]);
+ w_err(writer, SERD_ERR_BAD_TEXT, "invalid UTF-8 start: %X", utf8[0]);
*st = SERD_ERR_BAD_TEXT;
return 0;
case 1:
@@ -822,11 +838,10 @@ write_uri_node(SerdWriter* const writer,
if (!has_scheme && !supports_uriref(writer) &&
!serd_env_base_uri(writer->env)) {
- serd_world_errorf(writer->world,
- SERD_ERR_BAD_ARG,
- "syntax does not support URI reference <%s>\n",
- node_str);
- return SERD_ERR_BAD_ARG;
+ return w_err(writer,
+ SERD_ERR_BAD_ARG,
+ "syntax does not support URI reference <%s>",
+ node_str);
}
return write_full_uri_node(writer, node);
@@ -843,11 +858,10 @@ write_curie(SerdWriter* const writer, const SerdNode* const node)
if (writer->syntax == SERD_NTRIPLES || writer->syntax == SERD_NQUADS) {
if ((st = serd_env_expand_in_place(writer->env, node, &prefix, &suffix))) {
- serd_world_errorf(writer->world,
- st,
- "undefined namespace prefix `%s'\n",
- serd_node_string(node));
- return st;
+ return w_err(writer,
+ st,
+ "undefined namespace prefix in `%s'",
+ serd_node_string(node));
}
TRY(st, esink("<", 1, writer));
@@ -1236,10 +1250,10 @@ serd_writer_end_anon(SerdWriter* writer, const SerdNode* node)
}
if (writer->anon_stack_size == 0) {
- return serd_world_errorf(writer->world,
- SERD_ERR_UNKNOWN,
- "unexpected end of anonymous node `%s'\n",
- serd_node_string(node));
+ return w_err(writer,
+ SERD_ERR_BAD_CALL,
+ "unexpected end of anonymous node `%s'",
+ serd_node_string(node));
}
SerdStatus st = write_sep(writer, writer->context.flags, SEP_ANON_END);
diff --git a/test/meson.build b/test/meson.build
index 222ec1bc..4d9d2b43 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -10,6 +10,7 @@ unit_tests = [
'caret',
'env',
'free_null',
+ 'log',
'node',
'nodes',
'overflow',
@@ -102,6 +103,24 @@ if get_option('utils')
suite: ['serdi', 'options'])
endforeach
+ test('ansi_clicolor_force',
+ serdi,
+ args: files('bad/bad-lang.ttl'),
+ env: test_env + ['CLICOLOR_FORCE=1'],
+ should_fail: true)
+
+ test('ansi_clicolor_off',
+ serdi,
+ args: files('bad/bad-lang.ttl'),
+ env: test_env + ['CLICOLOR=0'],
+ should_fail: true)
+
+ test('ansi_no_color',
+ serdi,
+ args: files('bad/bad-lang.ttl'),
+ env: test_env + ['NO_COLOR=1'],
+ should_fail: true)
+
test('none',
serdi,
env: test_env,
diff --git a/test/test_log.c b/test/test_log.c
new file mode 100644
index 00000000..88d14dc6
--- /dev/null
+++ b/test/test_log.c
@@ -0,0 +1,128 @@
+/*
+ Copyright 2021 David Robillard <d@drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#undef NDEBUG
+
+#include "serd/serd.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <string.h>
+
+static SerdStatus
+custom_log_func(void* const handle,
+ const SerdLogLevel level,
+ const size_t n_fields,
+ const SerdLogField* const fields,
+ const SerdStringView message)
+{
+ (void)message;
+
+ bool* const called = (bool*)handle;
+
+ assert(level == SERD_LOG_LEVEL_NOTICE);
+ assert(n_fields == 1);
+ assert(!strcmp(fields[0].key, "TEST_KEY"));
+ assert(!strcmp(fields[0].value, "TEST VALUE"));
+ assert(!strcmp(message.buf, "test message 42"));
+ assert(message.len == strlen("test message 42"));
+
+ *called = true;
+ return SERD_SUCCESS;
+}
+
+static void
+test_bad_arg(void)
+{
+ SerdWorld* const world = serd_world_new();
+ bool called = false;
+
+ serd_set_log_func(world, custom_log_func, &called);
+
+ assert(serd_logf(world, SERD_LOG_LEVEL_ERROR, "%s", "") == SERD_ERR_BAD_ARG);
+
+ serd_world_free(world);
+}
+
+static void
+test_default_log(void)
+{
+ SerdWorld* const world = serd_world_new();
+
+ for (unsigned i = 0; i <= SERD_LOG_LEVEL_DEBUG; ++i) {
+ const SerdLogLevel level = (SerdLogLevel)i;
+
+ assert(!serd_logf(world, level, "test"));
+ }
+
+ serd_world_free(world);
+}
+
+static void
+test_custom_log(void)
+{
+ SerdWorld* const world = serd_world_new();
+ bool called = false;
+
+ serd_set_log_func(world, custom_log_func, &called);
+
+ const SerdLogField fields[1] = {{"TEST_KEY", "TEST VALUE"}};
+ assert(!serd_xlogf(
+ world, SERD_LOG_LEVEL_NOTICE, 1, fields, "test message %d", 42));
+
+ assert(called);
+ serd_world_free(world);
+}
+
+static void
+test_caret(void)
+{
+ SerdWorld* const world = serd_world_new();
+ SerdNode* const name = serd_new_string(SERD_STRING("filename"));
+ SerdCaret* const caret = serd_caret_new(name, 46, 2);
+
+ serd_logf_at(world, SERD_LOG_LEVEL_NOTICE, caret, "is just ahead of me");
+ serd_logf_at(world, SERD_LOG_LEVEL_NOTICE, NULL, "isn't");
+
+ serd_caret_free(caret);
+ serd_node_free(name);
+ serd_world_free(world);
+}
+
+static void
+test_filename_only(void)
+{
+ SerdWorld* const world = serd_world_new();
+
+ const SerdLogField fields[2] = {{"TEST_KEY", "TEST VALUE"},
+ {"SERD_FILE", "somename"}};
+
+ assert(!serd_xlogf(world, SERD_LOG_LEVEL_INFO, 2, fields, "no numbers here"));
+
+ serd_world_free(world);
+}
+
+int
+main(void)
+{
+ test_bad_arg();
+ test_default_log();
+ test_custom_log();
+ test_caret();
+ test_filename_only();
+
+ return 0;
+}