From 91c0c68e153118f4d64c137d78882ee95830c307 Mon Sep 17 00:00:00 2001
From: David Robillard <d@drobilla.net>
Date: Wed, 13 Jul 2022 17:55:16 -0400
Subject: Move serdi to tools subdirectory

This separates the command-line tool code from the library implementation.
---
 meson.build                     |  19 +--
 src/.clang-tidy                 |   2 -
 src/console.c                   |  41 ------
 src/console.h                   |  10 --
 src/serdi.c                     | 290 ----------------------------------------
 test/run_suite.py               |   2 +-
 test/serd_test_util/__init__.py |   2 +-
 tools/.clang-tidy               |  12 ++
 tools/console.c                 |  41 ++++++
 tools/console.h                 |  15 +++
 tools/meson.build               |  23 ++++
 tools/serdi.c                   | 290 ++++++++++++++++++++++++++++++++++++++++
 12 files changed, 384 insertions(+), 363 deletions(-)
 delete mode 100644 src/console.c
 delete mode 100644 src/console.h
 delete mode 100644 src/serdi.c
 create mode 100644 tools/.clang-tidy
 create mode 100644 tools/console.c
 create mode 100644 tools/console.h
 create mode 100644 tools/meson.build
 create mode 100644 tools/serdi.c

diff --git a/meson.build b/meson.build
index 66ef536f..2c440224 100644
--- a/meson.build
+++ b/meson.build
@@ -234,24 +234,7 @@ install_headers(c_headers, subdir: versioned_name / 'serd')
 
 # Build serdi command line utility
 if not get_option('tools').disabled()
-  tool_link_args = []
-  if get_option('static')
-    tool_link_args += ['-static']
-  endif
-
-  serdi = executable(
-    'serdi',
-    files(
-      'src/console.c',
-      'src/serdi.c',
-    ),
-    c_args: c_suppressions + platform_c_args,
-    dependencies: [serd_dep, zix_dep],
-    install: true,
-    link_args: tool_link_args,
-  )
-
-  meson.override_find_program('serdi', serdi)
+  subdir('tools')
 endif
 
 # Display top-level summary (before subdirectories to appear first)
diff --git a/src/.clang-tidy b/src/.clang-tidy
index d02d9891..77b43fc1 100644
--- a/src/.clang-tidy
+++ b/src/.clang-tidy
@@ -5,10 +5,8 @@ Checks: >
   -*-magic-numbers,
   -bugprone-easily-swappable-parameters,
   -cert-err33-c,
-  -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,
   -clang-analyzer-valist.Uninitialized,
   -clang-diagnostic-unused-function,
-  -concurrency-mt-unsafe,
   -hicpp-multiway-paths-covered,
   -hicpp-signed-bitwise,
   -llvm-header-guard,
diff --git a/src/console.c b/src/console.c
deleted file mode 100644
index 4a127c3e..00000000
--- a/src/console.c
+++ /dev/null
@@ -1,41 +0,0 @@
-// 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/src/console.h b/src/console.h
deleted file mode 100644
index 65b041ba..00000000
--- a/src/console.h
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright 2021 David Robillard <d@drobilla.net>
-// SPDX-License-Identifier: ISC
-
-#include <stdio.h>
-
-void
-serd_set_stream_utf8_mode(FILE* stream);
-
-int
-serd_print_version(const char* program);
diff --git a/src/serdi.c b/src/serdi.c
deleted file mode 100644
index fd7fdb15..00000000
--- a/src/serdi.c
+++ /dev/null
@@ -1,290 +0,0 @@
-// 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;
-}
diff --git a/test/run_suite.py b/test/run_suite.py
index 2e93502f..b44808b7 100755
--- a/test/run_suite.py
+++ b/test/run_suite.py
@@ -154,7 +154,7 @@ def main():
     parser.add_argument("--lax", action="store_true", help="tolerate errors")
     parser.add_argument("--report", help="path to write result report to")
     parser.add_argument("--reverse", action="store_true", help="reverse test")
-    parser.add_argument("--serdi", default="serdi", help="path to serdi")
+    parser.add_argument("--serdi", default="tools/serdi", help="path to serdi")
     parser.add_argument("--wrapper", default="", help="executable wrapper")
     parser.add_argument("manifest", help="test suite manifest.ttl file")
     parser.add_argument("base_uri", help="base URI for tests")
diff --git a/test/serd_test_util/__init__.py b/test/serd_test_util/__init__.py
index 260dcd9d..4f426009 100644
--- a/test/serd_test_util/__init__.py
+++ b/test/serd_test_util/__init__.py
@@ -57,7 +57,7 @@ def wrapper_args(description, with_input=False):
     """Return the command line arguments for a wrapped test."""
 
     parser = argparse.ArgumentParser(description)
-    parser.add_argument("--serdi", default="./serdi", help="serdi executable")
+    parser.add_argument("--serdi", default="tools/serdi", help="executable")
     parser.add_argument("--wrapper", default="", help="executable wrapper")
     if with_input:
         parser.add_argument("input", help="input file")
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;
+}
-- 
cgit v1.2.1