From 3cc5a4fcaea8f1e2fd47c53135b53f8edcb8619d Mon Sep 17 00:00:00 2001 From: David Robillard Date: Wed, 5 Apr 2023 07:01:18 -0400 Subject: Factor out and rewrite command-line interface --- tools/console.c | 331 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 315 insertions(+), 16 deletions(-) (limited to 'tools/console.c') diff --git a/tools/console.c b/tools/console.c index 94c6dc79..2a861d1b 100644 --- a/tools/console.c +++ b/tools/console.c @@ -3,8 +3,15 @@ #include "console.h" -#include "serd/serd.h" +#include "serd/log.h" +#include "serd/node.h" +#include "serd/stream.h" +#include "serd/string.h" +#include "serd/syntax.h" +#include "serd/uri.h" +#include "serd/version.h" #include "zix/allocator.h" +#include "zix/attributes.h" #include "zix/filesystem.h" #ifdef _WIN32 @@ -15,9 +22,86 @@ # include #endif +#include +#include +#include #include +#include #include +#define MAX_DEPTH 128U + +ZIX_PURE_FUNC bool +serd_option_iter_is_end(const OptionIter iter) +{ + return iter.a >= iter.argc || iter.argv[iter.a][0] != '-' || + !iter.argv[iter.a][iter.f]; +} + +SerdStatus +serd_option_iter_advance(OptionIter* const iter) +{ + if (!iter->argv[iter->a][++iter->f]) { + ++iter->a; + iter->f = 1; + } + + return SERD_SUCCESS; +} + +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_tool_output(out_path)).stream)) { + fprintf(stderr, + "%s: failed to open output file (%s)\n", + program, + strerror(errno)); + return SERD_BAD_STREAM; + } + + // We have something to write to, so build the writing environment + const SerdLimits limits = {options.stack_size, MAX_DEPTH}; + if (!(tool->world = serd_world_new(NULL)) || + serd_world_set_limits(tool->world, limits) || + !(tool->env = serd_create_env( + NULL, 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, + options.block_size))) { + fprintf(stderr, "%s: failed to set up writing environment\n", program); + return SERD_UNKNOWN_ERROR; + } + + return SERD_SUCCESS; +} + +SerdStatus +serd_tool_cleanup(SerdTool tool) +{ + SerdStatus st = SERD_SUCCESS; + if (tool.out.stream) { + // Close the output stream explicitly to check if there were any errors + if ((st = serd_close_output(&tool.out))) { + perror("write error"); + } + } + + serd_writer_free(tool.writer); + serd_env_free(tool.env); + serd_world_free(tool.world); + return st; +} + void serd_set_stream_utf8_mode(FILE* const stream) { @@ -28,7 +112,7 @@ serd_set_stream_utf8_mode(FILE* const stream) #endif } -int +SerdStatus serd_print_version(const char* const program) { printf("%s %d.%d.%d \n", @@ -42,34 +126,90 @@ 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_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_BAD_ARG; + } + + *argument = (size_t)size; + return SERD_SUCCESS; } SerdStatus serd_set_base_uri_from_path(SerdEnv* const env, const char* const path) { - char* const input_path = zix_canonical_path(NULL, path); - if (!input_path) { + const size_t path_len = strlen(path); + char* const real_path = zix_canonical_path(NULL, path); + if (!real_path) { return SERD_BAD_ARG; } - SerdNode* const file_uri = serd_node_new( - NULL, serd_a_file_uri(serd_string(input_path), serd_empty_string())); + 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 + 1); + base_path[real_path_len] = path[path_len - 1]; - serd_env_set_base_uri(env, serd_node_string_view(file_uri)); - serd_node_free(NULL, file_uri); - zix_free(NULL, input_path); + base_node = serd_node_new( + NULL, serd_a_file_uri(serd_string(base_path), serd_empty_string())); + + free(base_path); + } else { + base_node = serd_node_new( + NULL, serd_a_file_uri(serd_string(real_path), serd_empty_string())); + } + + serd_env_set_base_uri(env, serd_node_string_view(base_node)); + serd_node_free(NULL, base_node); + zix_free(NULL, real_path); return SERD_SUCCESS; } SerdSyntax -serd_choose_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); @@ -118,7 +258,26 @@ serd_set_input_option(const SerdStringView name, } } - return SERD_FAILURE; + return SERD_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 @@ -154,9 +313,87 @@ serd_set_output_option(const SerdStringView name, } } + return SERD_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(SerdAllocator* const allocator, + 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(allocator, serd_string(base_string)); + } + + SerdEnv* const env = serd_env_new(allocator, serd_empty_string()); + if (base_string && base_string[0]) { + const SerdStatus st = serd_set_base_uri_from_path(env, base_string); + if (st) { + fprintf(stderr, "%s: invalid base URI \"%s\"\n", program, base_string); + serd_env_free(env); + return NULL; + } + } + + return env; +} + /// Wrapper for getc that is compatible with SerdReadFunc but faster than fread static size_t serd_file_read_byte(void* buf, size_t size, size_t nmemb, void* stream) @@ -201,3 +438,65 @@ serd_open_tool_output(const char* const filename) return serd_open_output_file(filename); } + +SerdStatus +serd_read_source(SerdWorld* const world, + const SerdCommonOptions opts, + SerdEnv* const env, + const SerdSyntax syntax, + SerdInputStream* const in, + const char* const name, + const SerdSink* const sink) +{ + SerdReader* const reader = + serd_reader_new(world, syntax, opts.input.flags, env, sink); + + SerdNode* const name_node = serd_node_new(NULL, serd_a_string(name)); + SerdStatus st = serd_reader_start(reader, in, name_node, opts.block_size); + serd_node_free(NULL, name_node); + 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 + SerdInputStream in = serd_open_tool_input(in_path); + if (!in.stream) { + return SERD_BAD_ARG; + } + + // Read the entire file + st = serd_read_source( + world, + opts, + env, + serd_choose_syntax(world, opts.input, in_path, SERD_TRIG), + &in, + !strcmp(in_path, "-") ? "stdin" : in_path, + sink); + + serd_close_input(&in); + } + + return st; +} -- cgit v1.2.1