diff options
author | David Robillard <d@drobilla.net> | 2021-10-21 15:38:10 -0400 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2022-01-28 21:57:07 -0500 |
commit | b404312686874e539b617d1f27ccbaa5a82936af (patch) | |
tree | c2fdb2cc046e6da53071629cd1750dcc327e6cd9 /tools/console.c | |
parent | d4aec28ba8ad24d5aef3ee12beeb1b805148eab1 (diff) | |
download | serd-b404312686874e539b617d1f27ccbaa5a82936af.tar.gz serd-b404312686874e539b617d1f27ccbaa5a82936af.tar.bz2 serd-b404312686874e539b617d1f27ccbaa5a82936af.zip |
Replace serdi with more fine-grained tools
Especially with the new functionality, the complexity of the command-line
interface alone was really becoming unmanageable. The serdi implementation
also had the highest cyclomatic complexity of the entire codebase by a huge
margin.
So, take a page from the Unix philosophy and split serdi into several more
finely-honed tools that can be freely composed. Though there is still
unfortunately quite a bit of option overlap between them due to the common
details of reading RDF, I think the resulting tools are a lot easier to
understand, both from a user and a developer perspective.
Diffstat (limited to 'tools/console.c')
-rw-r--r-- | tools/console.c | 288 |
1 files changed, 272 insertions, 16 deletions
diff --git a/tools/console.c b/tools/console.c index ea5fd7ee..f1e78d75 100644 --- a/tools/console.c +++ b/tools/console.c @@ -26,9 +26,64 @@ # include <io.h> #endif +#include <errno.h> +#include <limits.h> #include <stdint.h> +#include <stdlib.h> #include <string.h> +SerdStatus +serd_tool_setup(SerdTool* const tool, + const char* const program, + SerdCommonOptions options) +{ + // Open the output first, since if that fails we have nothing to do + const char* const out_path = options.out_filename; + if (!(tool->out = serd_open_output(out_path, options.block_size))) { + fprintf(stderr, + "%s: failed to open output file (%s)\n", + program, + strerror(errno)); + return SERD_ERR_UNKNOWN; + } + + // We have something to write to, so build the writing environment + if (!(tool->world = serd_world_new()) || + !(tool->env = + serd_create_env(program, options.base_uri, options.out_filename)) || + !(tool->writer = serd_writer_new( + tool->world, + serd_choose_syntax( + tool->world, options.output, options.out_filename, SERD_NQUADS), + options.output.flags, + tool->env, + tool->out))) { + fprintf(stderr, "%s: failed to set up writing environment\n", program); + return SERD_ERR_INTERNAL; + } + + return SERD_SUCCESS; +} + +SerdStatus +serd_tool_cleanup(const SerdTool tool) +{ + SerdStatus st = SERD_SUCCESS; + if (tool.out) { + // Close the output stream explicitly to check if there were any errors + if (serd_byte_sink_close(tool.out)) { + perror("write error"); + st = SERD_ERR_BAD_WRITE; + } + } + + serd_writer_free(tool.writer); + serd_env_free(tool.env); + serd_world_free(tool.world); + serd_byte_sink_free(tool.out); + return st; +} + void serd_set_stream_utf8_mode(FILE* const stream) { @@ -39,7 +94,7 @@ serd_set_stream_utf8_mode(FILE* const stream) #endif } -int +SerdStatus serd_print_version(const char* const program) { printf("%s %d.%d.%d <http://drobilla.net/software/serd>\n", @@ -53,7 +108,43 @@ serd_print_version(const char* const program) "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; + return SERD_FAILURE; +} + +SerdStatus +serd_get_argument(OptionIter* const iter, const char** const argument) +{ + const char flag = iter->argv[iter->a][iter->f++]; + + if (iter->argv[iter->a][iter->f] || (iter->a + 1) == iter->argc) { + fprintf( + stderr, "%s: option requires an argument -- %c\n", iter->argv[0], flag); + return SERD_ERR_BAD_ARG; + } + + *argument = iter->argv[++iter->a]; + ++iter->a; + iter->f = 1; + return SERD_SUCCESS; +} + +SerdStatus +serd_get_size_argument(OptionIter* const iter, size_t* const argument) +{ + SerdStatus st = SERD_SUCCESS; + const char* string = NULL; + if ((st = serd_get_argument(iter, &string))) { + return st; + } + + char* endptr = NULL; + const long size = strtol(string, &endptr, 10); + if (size <= 0 || size == LONG_MAX || *endptr != '\0') { + return SERD_ERR_BAD_ARG; + } + + *argument = (size_t)size; + return SERD_SUCCESS; } SerdStatus @@ -89,8 +180,26 @@ serd_set_input_option(const SerdStringView name, } } - // SERDI_ERRORF("invalid input option `%s'\n", name.buf); - return SERD_FAILURE; + return SERD_ERR_BAD_ARG; +} + +SerdStatus +serd_parse_input_argument(OptionIter* const iter, + SerdSyntaxOptions* const options) +{ + SerdStatus st = SERD_SUCCESS; + const char* argument = NULL; + + if (!(st = serd_get_argument(iter, &argument))) { + if ((st = serd_set_input_option( + SERD_STRING(argument), &options->syntax, &options->flags))) { + fprintf(stderr, "%s: unknown option \"%s\"\n", iter->argv[0], argument); + } else if (!strcmp(argument, "empty") || options->syntax) { + options->overridden = true; + } + } + + return st; } SerdStatus @@ -126,16 +235,90 @@ serd_set_output_option(const SerdStringView name, } } + return SERD_ERR_BAD_ARG; +} + +SerdStatus +serd_parse_output_argument(OptionIter* const iter, + SerdSyntaxOptions* const options) +{ + SerdStatus st = SERD_SUCCESS; + const char* argument = NULL; + + if (!(st = serd_get_argument(iter, &argument))) { + if ((st = serd_set_output_option( + SERD_STRING(argument), &options->syntax, &options->flags))) { + fprintf(stderr, "%s: unknown option \"%s\"\n", iter->argv[0], argument); + } else if (!strcmp(argument, "empty") || options->syntax) { + options->overridden = true; + } + } + + return st; +} + +SerdStatus +serd_parse_common_option(OptionIter* const iter, SerdCommonOptions* const opts) +{ + const char opt = iter->argv[iter->a][iter->f]; + switch (opt) { + case 'B': + return serd_get_argument(iter, &opts->base_uri); + + case 'I': + return serd_parse_input_argument(iter, &opts->input); + + case 'O': + return serd_parse_output_argument(iter, &opts->output); + + case 'b': + return serd_get_size_argument(iter, &opts->block_size); + + case 'k': + return serd_get_size_argument(iter, &opts->stack_size); + + case 'o': + return serd_get_argument(iter, &opts->out_filename); + + default: + break; + } + return SERD_FAILURE; } +SerdEnv* +serd_create_env(const char* const program, + const char* const base_string, + const char* const out_filename) +{ + const bool is_rebase = base_string && !strcmp(base_string, "rebase"); + if (is_rebase && !out_filename) { + fprintf(stderr, "%s: rebase requires an output filename\n", program); + return NULL; + } + + if (base_string && serd_uri_string_has_scheme(base_string)) { + return serd_env_new(SERD_STRING(base_string)); + } + + SerdEnv* const env = serd_env_new(SERD_EMPTY_STRING()); + serd_set_base_uri_from_path(env, is_rebase ? out_filename : base_string); + return env; +} + SerdSyntax -serd_choose_input_syntax(SerdWorld* const world, - const SerdSyntax requested, - const char* const filename) +serd_choose_syntax(SerdWorld* const world, + const SerdSyntaxOptions options, + const char* const filename, + const SerdSyntax fallback) { - if (requested) { - return requested; + if (options.overridden || options.syntax != SERD_SYNTAX_EMPTY) { + return options.syntax; + } + + if (!filename || !strcmp(filename, "-")) { + return fallback; } const SerdSyntax guessed = serd_guess_syntax(filename); @@ -202,17 +385,90 @@ serd_open_output(const char* const filename, const size_t block_size) SerdStatus serd_set_base_uri_from_path(SerdEnv* const env, const char* const path) { - char* const input_path = serd_canonical_path(path); - if (!input_path) { + const size_t path_len = path ? strlen(path) : 0u; + if (!path_len) { return SERD_ERR_BAD_ARG; } - SerdNode* const file_uri = - serd_new_file_uri(SERD_STRING(input_path), SERD_EMPTY_STRING()); + char* const real_path = serd_canonical_path(path); + if (!real_path) { + return SERD_ERR_BAD_ARG; + } + + const size_t real_path_len = strlen(real_path); + SerdNode* base_node = NULL; + if (path[path_len - 1] == '/' || path[path_len - 1] == '\\') { + char* const base_path = (char*)calloc(real_path_len + 2, 1); + memcpy(base_path, real_path, real_path_len); + base_path[real_path_len] = path[path_len - 1]; + + base_node = serd_new_file_uri(SERD_STRING(base_path), SERD_EMPTY_STRING()); + free(base_path); + } else { + base_node = serd_new_file_uri(SERD_STRING(real_path), SERD_EMPTY_STRING()); + } - serd_env_set_base_uri(env, serd_node_string_view(file_uri)); - serd_node_free(file_uri); - serd_free(input_path); + serd_env_set_base_uri(env, serd_node_string_view(base_node)); + serd_node_free(base_node); + serd_free(real_path); return SERD_SUCCESS; } + +SerdStatus +serd_read_source(SerdWorld* const world, + const SerdCommonOptions opts, + SerdEnv* const env, + const SerdSyntax syntax, + SerdByteSource* const in, + const SerdSink* const sink) +{ + SerdReader* const reader = serd_reader_new( + world, syntax, opts.input.flags, env, sink, opts.stack_size); + + SerdStatus st = serd_reader_start(reader, in); + if (!st) { + st = serd_reader_read_document(reader); + } + + serd_reader_free(reader); + return st; +} + +SerdStatus +serd_read_inputs(SerdWorld* const world, + const SerdCommonOptions opts, + SerdEnv* const env, + const intptr_t n_inputs, + char* const* const inputs, + const SerdSink* const sink) +{ + SerdStatus st = SERD_SUCCESS; + + for (intptr_t i = 0; !st && i < n_inputs; ++i) { + // Use the filename as the base URI if possible if user didn't override it + const char* const in_path = inputs[i]; + if (!opts.base_uri[0] && strcmp(in_path, "-")) { + serd_set_base_uri_from_path(env, in_path); + } + + // Open the input stream + SerdByteSource* const in = serd_open_input(in_path, opts.block_size); + if (!in) { + return SERD_ERR_BAD_ARG; + } + + // Read the entire file + st = serd_read_source( + world, + opts, + env, + serd_choose_syntax(world, opts.input, in_path, SERD_TRIG), + in, + sink); + + serd_byte_source_free(in); + } + + return st; +} |