diff options
author | David Robillard <d@drobilla.net> | 2023-04-05 07:01:18 -0400 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2023-12-02 18:49:08 -0500 |
commit | 3cc5a4fcaea8f1e2fd47c53135b53f8edcb8619d (patch) | |
tree | 4496d7af98b1033630d992db1b8788077ebda8f6 /tools/serd-pipe.c | |
parent | 89acd9d099bd46c1cbf17ee3a7bb78610a8138fe (diff) | |
download | serd-3cc5a4fcaea8f1e2fd47c53135b53f8edcb8619d.tar.gz serd-3cc5a4fcaea8f1e2fd47c53135b53f8edcb8619d.tar.bz2 serd-3cc5a4fcaea8f1e2fd47c53135b53f8edcb8619d.zip |
Factor out and rewrite command-line interface
Diffstat (limited to 'tools/serd-pipe.c')
-rw-r--r-- | tools/serd-pipe.c | 441 |
1 files changed, 156 insertions, 285 deletions
diff --git a/tools/serd-pipe.c b/tools/serd-pipe.c index 66600006..bbed9fa8 100644 --- a/tools/serd-pipe.c +++ b/tools/serd-pipe.c @@ -4,344 +4,215 @@ #include "console.h" #include "serd/canon.h" -#include "serd/env.h" #include "serd/input_stream.h" #include "serd/log.h" -#include "serd/node.h" -#include "serd/output_stream.h" #include "serd/reader.h" #include "serd/sink.h" #include "serd/status.h" #include "serd/string_view.h" #include "serd/syntax.h" -#include "serd/world.h" #include "serd/writer.h" -#include "zix/allocator.h" -#include "zix/filesystem.h" -#include <errno.h> -#include <limits.h> #include <stdbool.h> +#include <stdint.h> #include <stdio.h> -#include <stdlib.h> #include <string.h> -#define SERDI_ERROR(msg) fprintf(stderr, "serd-pipe: " msg) -#define SERDI_ERRORF(fmt, ...) fprintf(stderr, "serd-pipe: " fmt, __VA_ARGS__) +/* Application (after parsing command-line arguments) */ -#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" - " -B BASE_URI Base URI.\n" - " -C Convert literals to canonical form.\n" - " -I SYNTAX Input syntax (turtle/ntriples/trig/nquads),\n" - " or flag (lax/variables/verbatim).\n" - " -O SYNTAX Output syntax (empty/turtle/ntriples/nquads),\n" - " or flag (ascii/expanded/verbatim/terse/lax).\n" - " -b BYTES I/O block size.\n" - " -h Display this help and exit.\n" - " -k BYTES Parser stack size.\n" - " -q Suppress all output except data.\n" - " -r ROOT_URI Keep relative URIs within ROOT_URI.\n" - " -s STRING Parse STRING as input.\n" - " -v Display version information and exit.\n" - " -w FILENAME Write output to FILENAME instead of stdout.\n"; - - FILE* const os = error ? stderr : stdout; - fprintf(os, "%s", error ? "\n" : ""); - fprintf(os, "Usage: %s [OPTION]... INPUT...\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); -} +// All options +typedef struct { + SerdCommonOptions common; + const char* root_uri; + const char* input_string; + char* const* inputs; + intptr_t n_inputs; + bool canonical; + bool quiet; +} Options; +// Run the tool using the given options static SerdStatus -read_file(SerdWorld* const world, - const SerdSyntax syntax, - const SerdReaderFlags flags, - SerdEnv* const env, - const SerdSink* const sink, - const size_t stack_size, - const char* const filename, - const size_t block_size) +run(const Options opts) { - SerdInputStream in = serd_open_tool_input(filename); - if (!in.stream) { - SERDI_ERRORF( - "failed to open input file `%s' (%s)\n", filename, strerror(errno)); + SerdTool app = {{NULL, NULL, NULL, NULL}, NULL, NULL, NULL}; - return SERD_BAD_STREAM; + // Set up the writing environment + SerdStatus st = SERD_SUCCESS; + if ((st = serd_tool_setup(&app, "serd-pipe", opts.common))) { + serd_tool_cleanup(app); + return st; } - const SerdLimits limits = {stack_size, MAX_DEPTH}; - serd_world_set_limits(world, limits); + if (opts.quiet) { + serd_set_log_func(app.world, serd_quiet_log_func, NULL); + } - SerdReader* reader = serd_reader_new(world, syntax, flags, env, sink); - SerdStatus st = serd_reader_start(reader, &in, NULL, block_size); + serd_writer_set_root_uri(app.writer, serd_string(opts.root_uri)); - st = st ? st : serd_reader_read_document(reader); + // Set up the output pipeline: [canon] -> writer + const SerdSink* const target = serd_writer_sink(app.writer); + const SerdSink* sink = target; + SerdSink* canon = NULL; + if (opts.canonical) { + canon = serd_canon_new(app.world, target, opts.common.input.flags); + sink = canon; + } - serd_reader_free(reader); - serd_close_input(&in); + if (opts.input_string) { + const char* position = opts.input_string; + SerdInputStream in = serd_open_input_string(&position); - return st; -} + st = serd_read_source( + app.world, + opts.common, + app.env, + serd_choose_syntax(app.world, opts.common.input, NULL, SERD_TRIG), + &in, + "string", + sink); -int -main(int argc, char** argv) -{ - const char* const prog = argv[0]; - - SerdNode* base = NULL; - SerdSyntax input_syntax = SERD_SYNTAX_EMPTY; - SerdSyntax output_syntax = SERD_SYNTAX_EMPTY; - SerdReaderFlags reader_flags = 0; - SerdWriterFlags writer_flags = 0; - bool osyntax_set = false; - bool canonical = false; - bool quiet = false; - size_t block_size = 4096U; - size_t stack_size = 1048576U; - const char* input_string = NULL; - const char* root_uri = NULL; - const char* out_filename = NULL; - int a = 1; - for (; a < argc && argv[a][0] == '-'; ++a) { - if (argv[a][1] == '\0') { - break; - } + serd_close_input(&in); + } - if (!strcmp(argv[a], "--help")) { - return print_usage(prog, false); - } + // Read all the inputs, which drives the writer to emit the output + if (st || + (st = serd_read_inputs( + app.world, opts.common, app.env, opts.n_inputs, opts.inputs, sink)) || + (st = serd_writer_finish(app.writer))) { + serd_sink_free(canon); + serd_tool_cleanup(app); + return st; + } - if (!strcmp(argv[a], "--version")) { - return serd_print_version(argv[0]); - } + serd_sink_free(canon); + return serd_tool_cleanup(app); +} - for (int o = 1; argv[a][o]; ++o) { - const char opt = argv[a][o]; - - if (opt == 'C') { - canonical = true; - } else if (opt == 'h') { - return print_usage(prog, false); - } else if (opt == 'q') { - quiet = true; - } else if (opt == 'v') { - return serd_print_version(argv[0]); - } else if (argv[a][1] == 'B') { - if (++a == argc) { - return missing_arg(prog, 'B'); - } - - base = serd_node_new(NULL, serd_a_uri_string(argv[a])); - break; - } else if (opt == 'I') { - if (argv[a][o + 1] || ++a == argc) { - return missing_arg(prog, 'I'); - } - - if (serd_set_input_option( - serd_string(argv[a]), &input_syntax, &reader_flags)) { - return print_usage(argv[0], true); - } - break; - } else if (opt == 'O') { - if (argv[a][o + 1] || ++a == argc) { - return missing_arg(prog, 'O'); - } - - if (serd_set_output_option( - serd_string(argv[a]), &output_syntax, &writer_flags)) { - return print_usage(argv[0], true); - } - - osyntax_set = - output_syntax != SERD_SYNTAX_EMPTY || !strcmp(argv[a], "empty"); - - break; - } else if (opt == 'b') { - if (argv[a][o + 1] || ++a == argc) { - return missing_arg(prog, 'b'); - } - - char* endptr = NULL; - const long size = strtol(argv[a], &endptr, 10); - if (size < 1 || size == LONG_MAX || *endptr != '\0') { - SERDI_ERRORF("invalid block size `%s'\n", argv[a]); - return 1; - } - block_size = (size_t)size; - 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 == 'r') { - if (argv[a][o + 1] || ++a == argc) { - return missing_arg(prog, 'r'); - } - - root_uri = argv[a]; - break; - } else if (opt == 's') { - if (argv[a][o + 1] || ++a == argc) { - return missing_arg(prog, 's'); - } - - input_string = argv[a]; - break; - } else if (opt == 'w') { - if (argv[a][o + 1] || ++a == argc) { - return missing_arg(argv[0], 'w'); - } - - out_filename = argv[a]; - break; - } else { - SERDI_ERRORF("invalid option -- '%s'\n", argv[a] + 1); - return print_usage(prog, true); - } - } - } +/* Command-line interface (before setting up serd) */ - if (a == argc && !input_string) { - SERDI_ERROR("missing input\n"); - return print_usage(prog, true); - } +static int +print_usage(const char* const name, const bool error) +{ + static const char* const description = + "Read and write RDF data.\n" + "INPUT can be a local filename, or \"-\" to read from standard input.\n\n" + " -B BASE_URI Base URI or path for resolving relative references.\n" + " -C Convert literals to canonical form.\n" + " -I SYNTAX Input syntax turtle/ntriples/trig/nquads, or option\n" + " lax/variables/relative/global/generated.\n" + " -O SYNTAX Output syntax empty/turtle/ntriples/nquads, or option\n" + " ascii/expanded/verbatim/terse/lax.\n" + " -R ROOT_URI Keep relative URIs within ROOT_URI.\n" + " -V Display version information and exit.\n" + " -b BYTES I/O block size.\n" + " -h Display this help and exit.\n" + " -k BYTES Parser stack size.\n" + " -o FILENAME Write output to FILENAME instead of stdout.\n" + " -q Suppress warning and error output.\n" + " -s STRING Parse STRING as input.\n"; - serd_set_stream_utf8_mode(stdin); + FILE* const os = error ? stderr : stdout; + fprintf(os, "%s", error ? "\n" : ""); + fprintf(os, "Usage: %s [OPTION]... [INPUT]...\n", name); + fprintf(os, "%s", description); + return error; +} - char* const* const inputs = argv + a; - const int n_inputs = argc - a; +// Parse the option pointed to by `iter`, and advance it to the next one +static SerdStatus +parse_option(OptionIter* const iter, Options* const opts) +{ +#define ARG_ERRORF(fmt, ...) \ + fprintf(stderr, "%s: " fmt, iter->argv[0], __VA_ARGS__) - bool input_has_graphs = serd_syntax_has_graphs(input_syntax); - for (int i = a; i < argc; ++i) { - if (serd_syntax_has_graphs(serd_guess_syntax(argv[i]))) { - input_has_graphs = true; - break; - } + SerdStatus st = serd_parse_common_option(iter, &opts->common); + if (st != SERD_FAILURE) { + return st; } - if (!output_syntax && !osyntax_set) { - output_syntax = input_has_graphs ? SERD_NQUADS : SERD_NTRIPLES; + if (!strcmp(iter->argv[iter->a], "--help")) { + print_usage(iter->argv[0], false); + return SERD_FAILURE; } - if (!base && n_inputs == 1 && - (output_syntax == SERD_NQUADS || output_syntax == SERD_NTRIPLES)) { - // Choose base URI from the single input path - char* const input_path = zix_canonical_path(NULL, inputs[0]); - if (!input_path || - !(base = serd_node_new( - NULL, - serd_a_file_uri(serd_string(input_path), serd_empty_string())))) { - SERDI_ERRORF("unable to determine base URI from path %s\n", inputs[0]); - } - zix_free(NULL, input_path); + if (!strcmp(iter->argv[iter->a], "--version")) { + return serd_print_version(iter->argv[0]); } - SerdWorld* const world = serd_world_new(NULL); - const SerdLimits limits = {stack_size, MAX_DEPTH}; - serd_world_set_limits(world, limits); - - SerdEnv* const env = serd_env_new( - NULL, base ? serd_node_string_view(base) : serd_empty_string()); + const char opt = iter->argv[iter->a][iter->f]; + switch (opt) { + case 'C': + opts->canonical = true; + return serd_option_iter_advance(iter); - SerdOutputStream out = serd_open_tool_output(out_filename); - if (!out.stream) { - perror("serdi: error opening output file"); - return 1; - } + case 'R': + return serd_get_argument(iter, &opts->root_uri); - SerdWriter* const writer = - serd_writer_new(world, output_syntax, writer_flags, env, &out, block_size); + case 'V': + return serd_print_version("serd-pipe"); - const SerdSink* sink = serd_writer_sink(writer); + case 'h': + print_usage(iter->argv[0], false); + return SERD_FAILURE; - SerdSink* canon = NULL; - if (canonical) { - sink = canon = serd_canon_new(world, sink, reader_flags); - } + case 'q': + opts->quiet = true; + return serd_option_iter_advance(iter); - if (quiet) { - serd_set_log_func(world, serd_quiet_log_func, NULL); - } + case 's': + return serd_get_argument(iter, &opts->input_string); - if (root_uri) { - serd_writer_set_root_uri(writer, serd_string(root_uri)); + default: + break; } - SerdStatus st = SERD_SUCCESS; - if (input_string) { - const char* position = input_string; - SerdInputStream string_in = serd_open_input_string(&position); + ARG_ERRORF("invalid option -- '%c'\n", opt); + return SERD_BAD_ARG; - SerdReader* const reader = serd_reader_new( - world, input_syntax ? input_syntax : SERD_TRIG, reader_flags, env, sink); +#undef ARG_ERRORF +} - if (!(st = serd_reader_start(reader, &string_in, NULL, 1U))) { - st = serd_reader_read_document(reader); +int +main(const int argc, char* const* const argv) +{ + char default_input[] = {'-', '\0'}; + char* default_inputs[] = {default_input}; + + Options opts = {{"", + NULL, + 4096U, + 1048576U, + {SERD_SYNTAX_EMPTY, 0U, false}, + {SERD_SYNTAX_EMPTY, 0U, false}}, + "", + NULL, + NULL, + 0U, + false, + false}; + + // Parse all command line options (which must precede inputs) + SerdStatus st = SERD_SUCCESS; + OptionIter iter = {argv, argc, 1, 1}; + while (!serd_option_iter_is_end(iter)) { + if ((st = parse_option(&iter, &opts))) { + return (st == SERD_FAILURE) ? 0 : print_usage(argv[0], true); } - - serd_reader_free(reader); - serd_close_input(&string_in); - } - - if (n_inputs == 1) { - reader_flags |= SERD_READ_GLOBAL; } - for (int i = 0; !st && i < n_inputs; ++i) { - if (!base && !!strcmp(inputs[i], "-")) { - if ((st = serd_set_base_uri_from_path(env, inputs[i]))) { - SERDI_ERRORF("failed to set base URI from path %s\n", inputs[i]); - break; - } - } - - if ((st = read_file(world, - serd_choose_syntax(world, input_syntax, inputs[i]), - reader_flags, - env, - sink, - stack_size, - inputs[i], - block_size))) { - break; - } + // Every argument past the last option is an input + opts.inputs = argv + iter.a; + opts.n_inputs = argc - iter.a; + if (opts.n_inputs + (bool)opts.input_string == 0) { + opts.n_inputs = 1; + opts.inputs = default_inputs; } - serd_sink_free(canon); - serd_writer_free(writer); - serd_env_free(env); - serd_node_free(NULL, base); - serd_world_free(world); - - if (serd_close_output(&out)) { - perror("serd-pipe: write error"); - st = SERD_BAD_STREAM; + // Don't add prefixes to blank node labels if there is only one input + if (opts.n_inputs + (bool)opts.input_string == 1) { + opts.common.input.flags |= SERD_READ_GLOBAL; } - return (st > SERD_FAILURE) ? 1 : 0; + return run(opts) > SERD_FAILURE; } |