aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2022-07-13 17:55:16 -0400
committerDavid Robillard <d@drobilla.net>2023-12-02 18:49:08 -0500
commit91c0c68e153118f4d64c137d78882ee95830c307 (patch)
tree1325868b64aab19ff57a08d528ac1a25b8e82d7a /tools
parent90fe1b64c2203f9e82646cb41c8a85f0a31125bb (diff)
downloadserd-91c0c68e153118f4d64c137d78882ee95830c307.tar.gz
serd-91c0c68e153118f4d64c137d78882ee95830c307.tar.bz2
serd-91c0c68e153118f4d64c137d78882ee95830c307.zip
Move serdi to tools subdirectory
This separates the command-line tool code from the library implementation.
Diffstat (limited to 'tools')
-rw-r--r--tools/.clang-tidy12
-rw-r--r--tools/console.c41
-rw-r--r--tools/console.h15
-rw-r--r--tools/meson.build23
-rw-r--r--tools/serdi.c290
5 files changed, 381 insertions, 0 deletions
diff --git a/tools/.clang-tidy b/tools/.clang-tidy
new file mode 100644
index 00000000..f7c82d78
--- /dev/null
+++ b/tools/.clang-tidy
@@ -0,0 +1,12 @@
+# Copyright 2020-2022 David Robillard <d@drobilla.net>
+# SPDX-License-Identifier: 0BSD OR ISC
+
+Checks: >
+ -*-magic-numbers,
+ -bugprone-easily-swappable-parameters,
+ -cert-err33-c,
+ -concurrency-mt-unsafe,
+ -hicpp-signed-bitwise,
+ -llvm-header-guard,
+ -readability-function-cognitive-complexity,
+InheritParentConfig: true
diff --git a/tools/console.c b/tools/console.c
new file mode 100644
index 00000000..4a127c3e
--- /dev/null
+++ b/tools/console.c
@@ -0,0 +1,41 @@
+// Copyright 2011-2021 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#include "console.h"
+
+#include "serd/serd.h"
+
+#ifdef _WIN32
+# ifdef _MSC_VER
+# define WIN32_LEAN_AND_MEAN 1
+# endif
+# include <fcntl.h>
+# include <io.h>
+#endif
+
+void
+serd_set_stream_utf8_mode(FILE* const stream)
+{
+#ifdef _WIN32
+ _setmode(_fileno(stream), _O_BINARY);
+#else
+ (void)stream;
+#endif
+}
+
+int
+serd_print_version(const char* const program)
+{
+ printf("%s %d.%d.%d <http://drobilla.net/software/serd>\n",
+ program,
+ SERD_MAJOR_VERSION,
+ SERD_MINOR_VERSION,
+ SERD_MICRO_VERSION);
+
+ printf("Copyright 2011-2023 David Robillard <d@drobilla.net>.\n"
+ "License: <http://www.opensource.org/licenses/isc>\n"
+ "This is free software; you are free to change and redistribute it.\n"
+ "There is NO WARRANTY, to the extent permitted by law.\n");
+
+ return 0;
+}
diff --git a/tools/console.h b/tools/console.h
new file mode 100644
index 00000000..41ab328d
--- /dev/null
+++ b/tools/console.h
@@ -0,0 +1,15 @@
+// Copyright 2021 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#ifndef SERD_TOOLS_CONSOLE_H
+#define SERD_TOOLS_CONSOLE_H
+
+#include <stdio.h>
+
+void
+serd_set_stream_utf8_mode(FILE* stream);
+
+int
+serd_print_version(const char* program);
+
+#endif // SERD_TOOLS_CONSOLE_H
diff --git a/tools/meson.build b/tools/meson.build
new file mode 100644
index 00000000..49fa32c7
--- /dev/null
+++ b/tools/meson.build
@@ -0,0 +1,23 @@
+# Copyright 2021-2022 David Robillard <d@drobilla.net>
+# SPDX-License-Identifier: 0BSD OR ISC
+
+tool_c_args = c_suppressions + platform_c_args
+tool_link_args = []
+
+if get_option('static') and cc.get_id() != 'msvc'
+ tool_link_args += ['-static']
+endif
+
+serdi = executable(
+ 'serdi',
+ files(
+ 'console.c',
+ 'serdi.c',
+ ),
+ c_args: tool_c_args,
+ dependencies: [serd_dep, zix_dep],
+ install: true,
+ link_args: tool_link_args,
+)
+
+meson.override_find_program('serdi', serdi)
diff --git a/tools/serdi.c b/tools/serdi.c
new file mode 100644
index 00000000..fd7fdb15
--- /dev/null
+++ b/tools/serdi.c
@@ -0,0 +1,290 @@
+// Copyright 2011-2023 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#include "console.h"
+
+#include "serd/env.h"
+#include "serd/error.h"
+#include "serd/input_stream.h"
+#include "serd/node.h"
+#include "serd/output_stream.h"
+#include "serd/reader.h"
+#include "serd/status.h"
+#include "serd/stream.h"
+#include "serd/string_view.h"
+#include "serd/syntax.h"
+#include "serd/world.h"
+#include "serd/writer.h"
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define SERDI_ERROR(msg) fprintf(stderr, "serdi: " msg)
+#define SERDI_ERRORF(fmt, ...) fprintf(stderr, "serdi: " fmt, __VA_ARGS__)
+
+#define MAX_DEPTH 128U
+
+static int
+print_usage(const char* const name, const bool error)
+{
+ static const char* const description =
+ "Read and write RDF syntax.\n"
+ "Use - for INPUT to read from standard input.\n\n"
+ " -a Write ASCII output.\n"
+ " -b Write output in blocks for performance.\n"
+ " -c PREFIX Chop PREFIX from matching blank node IDs.\n"
+ " -e Eat input one character at a time.\n"
+ " -f Fast and loose URI pass-through.\n"
+ " -h Display this help and exit.\n"
+ " -i SYNTAX Input syntax: turtle/ntriples/trig/nquads.\n"
+ " -k BYTES Parser stack size.\n"
+ " -l Lax (non-strict) parsing.\n"
+ " -o SYNTAX Output syntax: empty/turtle/ntriples/nquads.\n"
+ " -p PREFIX Add PREFIX to blank node IDs.\n"
+ " -q Suppress all output except data.\n"
+ " -r ROOT_URI Keep relative URIs within ROOT_URI.\n"
+ " -s INPUT Parse INPUT as string (terminates options).\n"
+ " -t Write terser output without newlines.\n"
+ " -v Display version information and exit.\n";
+
+ FILE* const os = error ? stderr : stdout;
+ fprintf(os, "%s", error ? "\n" : "");
+ fprintf(os, "Usage: %s [OPTION]... INPUT [BASE_URI]\n", name);
+ fprintf(os, "%s", description);
+ return error ? 1 : 0;
+}
+
+static int
+missing_arg(const char* const name, const char opt)
+{
+ SERDI_ERRORF("option requires an argument -- '%c'\n", opt);
+ return print_usage(name, true);
+}
+
+static SerdStatus
+quiet_error_func(void* const handle, const SerdError* const e)
+{
+ (void)handle;
+ (void)e;
+ return SERD_SUCCESS;
+}
+
+int
+main(int argc, char** argv)
+{
+ const char* const prog = argv[0];
+
+ SerdSyntax input_syntax = SERD_SYNTAX_EMPTY;
+ SerdSyntax output_syntax = SERD_SYNTAX_EMPTY;
+ SerdReaderFlags reader_flags = 0;
+ SerdWriterFlags writer_flags = 0;
+ bool from_string = false;
+ bool from_stdin = false;
+ bool bulk_read = true;
+ bool bulk_write = false;
+ bool osyntax_set = false;
+ bool quiet = false;
+ size_t stack_size = 1048576U;
+ const char* add_prefix = NULL;
+ const char* chop_prefix = NULL;
+ const char* root_uri = NULL;
+ int a = 1;
+ for (; a < argc && !from_string && argv[a][0] == '-'; ++a) {
+ if (argv[a][1] == '\0') {
+ from_stdin = true;
+ break;
+ }
+
+ if (!strcmp(argv[a], "--help")) {
+ return print_usage(prog, false);
+ }
+
+ if (!strcmp(argv[a], "--version")) {
+ return serd_print_version(argv[0]);
+ }
+
+ for (int o = 1; argv[a][o]; ++o) {
+ const char opt = argv[a][o];
+
+ if (opt == 'a') {
+ writer_flags |= SERD_WRITE_ASCII;
+ } else if (opt == 'b') {
+ bulk_write = true;
+ } else if (opt == 'e') {
+ bulk_read = false;
+ } else if (opt == 'f') {
+ writer_flags |= (SERD_WRITE_UNQUALIFIED | SERD_WRITE_UNRESOLVED);
+ } else if (opt == 'h') {
+ return print_usage(prog, false);
+ } else if (opt == 'l') {
+ reader_flags |= SERD_READ_LAX;
+ writer_flags |= SERD_WRITE_LAX;
+ } else if (opt == 'q') {
+ quiet = true;
+ } else if (opt == 't') {
+ writer_flags |= SERD_WRITE_TERSE;
+ } else if (opt == 'v') {
+ return serd_print_version(argv[0]);
+ } else if (opt == 's') {
+ from_string = true;
+ break;
+ } else if (opt == 'c') {
+ if (argv[a][o + 1] || ++a == argc) {
+ return missing_arg(prog, 'c');
+ }
+
+ chop_prefix = argv[a];
+ break;
+ } else if (opt == 'i') {
+ if (argv[a][o + 1] || ++a == argc) {
+ return missing_arg(prog, 'i');
+ }
+
+ if (!(input_syntax = serd_syntax_by_name(argv[a]))) {
+ return print_usage(prog, true);
+ }
+ break;
+ } else if (opt == 'k') {
+ if (argv[a][o + 1] || ++a == argc) {
+ return missing_arg(prog, 'k');
+ }
+
+ char* endptr = NULL;
+ const long size = strtol(argv[a], &endptr, 10);
+ if (size <= 0 || size == LONG_MAX || *endptr != '\0') {
+ SERDI_ERRORF("invalid stack size '%s'\n", argv[a]);
+ return 1;
+ }
+ stack_size = (size_t)size;
+ break;
+ } else if (opt == 'o') {
+ osyntax_set = true;
+ if (argv[a][o + 1] || ++a == argc) {
+ return missing_arg(prog, 'o');
+ }
+
+ if (!strcmp(argv[a], "empty")) {
+ output_syntax = SERD_SYNTAX_EMPTY;
+ } else if (!(output_syntax = serd_syntax_by_name(argv[a]))) {
+ return print_usage(argv[0], true);
+ }
+ break;
+ } else if (opt == 'p') {
+ if (argv[a][o + 1] || ++a == argc) {
+ return missing_arg(prog, 'p');
+ }
+
+ add_prefix = argv[a];
+ break;
+ } else if (opt == 'r') {
+ if (argv[a][o + 1] || ++a == argc) {
+ return missing_arg(prog, 'r');
+ }
+
+ root_uri = argv[a];
+ break;
+ } else {
+ SERDI_ERRORF("invalid option -- '%s'\n", argv[a] + 1);
+ return print_usage(prog, true);
+ }
+ }
+ }
+
+ if (a == argc) {
+ SERDI_ERROR("missing input\n");
+ return print_usage(prog, true);
+ }
+
+ serd_set_stream_utf8_mode(stdin);
+ serd_set_stream_utf8_mode(stdout);
+
+ const char* input = argv[a++];
+
+ if (!input_syntax && !(input_syntax = serd_guess_syntax(input))) {
+ input_syntax = SERD_TRIG;
+ }
+
+ const bool input_has_graphs = serd_syntax_has_graphs(input_syntax);
+ if (!output_syntax && !osyntax_set) {
+ output_syntax = input_has_graphs ? SERD_NQUADS : SERD_NTRIPLES;
+ }
+
+ SerdNode* base = NULL;
+ if (a < argc) { // Base URI given on command line
+ base = serd_new_uri(serd_string(argv[a]));
+ } else if (!from_string && !from_stdin) { // Use input file URI
+ base = serd_new_file_uri(serd_string(input), serd_empty_string());
+ }
+
+ FILE* const out_fd = stdout;
+ SerdWorld* const world = serd_world_new();
+ SerdEnv* const env =
+ serd_env_new(base ? serd_node_string_view(base) : serd_empty_string());
+
+ SerdOutputStream out = serd_open_output_stream((SerdWriteFunc)fwrite,
+ (SerdErrorFunc)ferror,
+ (SerdCloseFunc)fclose,
+ out_fd);
+
+ SerdWriter* const writer = serd_writer_new(
+ world, output_syntax, writer_flags, env, &out, bulk_write ? 4096U : 1U);
+
+ const SerdLimits limits = {stack_size, MAX_DEPTH};
+ serd_world_set_limits(world, limits);
+
+ SerdReader* const reader = serd_reader_new(
+ world, input_syntax, reader_flags, serd_writer_sink(writer));
+
+ if (quiet) {
+ serd_world_set_error_func(world, quiet_error_func, NULL);
+ }
+
+ if (root_uri) {
+ serd_writer_set_root_uri(writer, serd_string(root_uri));
+ }
+
+ serd_writer_chop_blank_prefix(writer, chop_prefix);
+ serd_reader_add_blank_prefix(reader, add_prefix);
+
+ SerdStatus st = SERD_SUCCESS;
+ SerdNode* input_name = NULL;
+ const char* position = NULL;
+ SerdInputStream in = {NULL, NULL, NULL, NULL};
+ size_t block_size = 1U;
+ if (from_string) {
+ position = input;
+ in = serd_open_input_string(&position);
+ input_name = serd_new_string(serd_string("string"));
+ } else if (from_stdin) {
+ in = serd_open_input_stream(
+ (SerdReadFunc)fread, (SerdErrorFunc)ferror, (SerdCloseFunc)fclose, stdin);
+ input_name = serd_new_string(serd_string("stdin"));
+ } else {
+ block_size = bulk_read ? 4096U : 1U;
+ in = serd_open_input_file(input);
+ input_name = serd_new_string(serd_string(input));
+ }
+
+ if (!(st = serd_reader_start(reader, &in, input_name, block_size))) {
+ st = serd_reader_read_document(reader);
+ }
+
+ serd_reader_finish(reader);
+ serd_reader_free(reader);
+ serd_writer_finish(writer);
+ serd_writer_free(writer);
+ serd_node_free(input_name);
+ serd_env_free(env);
+ serd_node_free(base);
+ serd_world_free(world);
+
+ if (fclose(stdout)) {
+ perror("serdi: write error");
+ st = SERD_BAD_STREAM;
+ }
+
+ return (st > SERD_FAILURE) ? 1 : 0;
+}