diff options
Diffstat (limited to 'Userland/Libraries/LibC/getopt.cpp')
-rw-r--r-- | Userland/Libraries/LibC/getopt.cpp | 373 |
1 files changed, 55 insertions, 318 deletions
diff --git a/Userland/Libraries/LibC/getopt.cpp b/Userland/Libraries/LibC/getopt.cpp index c588aff02d..6e8d1a09e9 100644 --- a/Userland/Libraries/LibC/getopt.cpp +++ b/Userland/Libraries/LibC/getopt.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include <AK/OptionParser.h> #include <AK/StringView.h> #include <AK/Vector.h> #include <getopt.h> @@ -20,338 +21,74 @@ char* optarg = nullptr; // POSIX says, "When an element of argv[] contains multiple option characters, // it is unspecified how getopt() determines which options have already been // processed". Well, this is how we do it. -static size_t s_index_into_multioption_argument = 0; - -[[gnu::format(printf, 1, 2)]] static inline void report_error(char const* format, ...) -{ - if (!opterr) - return; - - fputs("\033[31m", stderr); - - va_list ap; - va_start(ap, format); - vfprintf(stderr, format, ap); - va_end(ap); - - fputs("\033[0m\n", stderr); -} - namespace { - -class OptionParser { -public: - OptionParser(int argc, char* const* argv, StringView short_options, option const* long_options, int* out_long_option_index = nullptr); - int getopt(); - -private: - bool lookup_short_option(char option, int& needs_value) const; - int handle_short_option(); - - option const* lookup_long_option(char* raw) const; - int handle_long_option(); - - void shift_argv(); - bool find_next_option(); - - StringView current_arg() const - { - auto const* arg_ptr = m_argv[m_arg_index]; - if (arg_ptr == NULL) - return {}; - return { arg_ptr, strlen(arg_ptr) }; - } - - size_t m_argc { 0 }; - char* const* m_argv { nullptr }; - StringView m_short_options; - option const* m_long_options { nullptr }; - int* m_out_long_option_index { nullptr }; - bool m_stop_on_first_non_option { false }; - - size_t m_arg_index { 0 }; - size_t m_consumed_args { 0 }; -}; - -OptionParser::OptionParser(int argc, char* const* argv, StringView short_options, option const* long_options, int* out_long_option_index) - : m_argc(argc) - , m_argv(argv) - , m_short_options(short_options) - , m_long_options(long_options) - , m_out_long_option_index(out_long_option_index) -{ - // In the following case: - // $ foo bar -o baz - // we want to parse the option (-o baz) first, and leave the argument (bar) - // in argv after we return -1 when invoked the second time. So we reorder - // argv to put options first and positional arguments next. To turn this - // behavior off, start the short options spec with a "+". This is a GNU - // extension that we support. - m_stop_on_first_non_option = short_options.starts_with('+'); - - // See if we should reset the internal state. - if (optreset || optind == 0) { - optreset = 0; - optind = 1; - s_index_into_multioption_argument = 0; - } - - optopt = 0; - optarg = nullptr; -} - -int OptionParser::getopt() -{ - bool should_reorder_argv = !m_stop_on_first_non_option; - int res = -1; - - bool found_an_option = find_next_option(); - auto arg = current_arg(); - - if (!found_an_option) { - res = -1; - if (arg == "--") - m_consumed_args = 1; - else - m_consumed_args = 0; - } else { - // Alright, so we have an option on our hands! - bool is_long_option = arg.starts_with("--"sv); - if (is_long_option) - res = handle_long_option(); - else - res = handle_short_option(); - - // If we encountered an error, return immediately. - if (res == '?') - return '?'; - } - - if (should_reorder_argv) - shift_argv(); - else - VERIFY(optind == static_cast<int>(m_arg_index)); - optind += m_consumed_args; - - return res; -} - -bool OptionParser::lookup_short_option(char option, int& needs_value) const -{ - Vector<StringView> parts = m_short_options.split_view(option, SplitBehavior::KeepEmpty); - - VERIFY(parts.size() <= 2); - if (parts.size() < 2) { - // Haven't found the option in the spec. - return false; - } - - if (parts[1].starts_with("::"sv)) { - // If an option is followed by two colons, it optionally accepts an - // argument. - needs_value = optional_argument; - } else if (parts[1].starts_with(':')) { - // If it's followed by one colon, it requires an argument. - needs_value = required_argument; - } else { - // Otherwise, it doesn't accept arguments. - needs_value = no_argument; - } - return true; -} - -int OptionParser::handle_short_option() -{ - StringView arg = current_arg(); - VERIFY(arg.starts_with('-')); - - if (s_index_into_multioption_argument == 0) { - // Just starting to parse this argument, skip the "-". - s_index_into_multioption_argument = 1; - } - char option = arg[s_index_into_multioption_argument]; - s_index_into_multioption_argument++; - - int needs_value = no_argument; - bool ok = lookup_short_option(option, needs_value); - if (!ok) { - optopt = option; - report_error("Unrecognized option \033[1m-%c\033[22m", option); - return '?'; - } - - // Let's see if we're at the end of this argument already. - if (s_index_into_multioption_argument < arg.length()) { - // This not yet the end. - if (needs_value == no_argument) { - optarg = nullptr; - m_consumed_args = 0; - } else { - // Treat the rest of the argument as the value, the "-ovalue" - // syntax. - optarg = m_argv[m_arg_index] + s_index_into_multioption_argument; - // Next time, process the next argument. - s_index_into_multioption_argument = 0; - m_consumed_args = 1; - } - } else { - s_index_into_multioption_argument = 0; - if (needs_value != required_argument) { - optarg = nullptr; - m_consumed_args = 1; - } else if (m_arg_index + 1 < m_argc) { - // Treat the next argument as a value, the "-o value" syntax. - optarg = m_argv[m_arg_index + 1]; - m_consumed_args = 2; - } else { - report_error("Missing value for option \033[1m-%c\033[22m", option); - return '?'; - } - } - - return option; -} - -option const* OptionParser::lookup_long_option(char* raw) const -{ - StringView arg { raw, strlen(raw) }; - - for (size_t index = 0; m_long_options[index].name; index++) { - auto& option = m_long_options[index]; - StringView name { option.name, strlen(option.name) }; - - if (!arg.starts_with(name)) - continue; - - // It would be better to not write out the index at all unless we're - // sure we've found the right option, but whatever. - if (m_out_long_option_index) - *m_out_long_option_index = index; - - // Can either be "--option" or "--option=value". - if (arg.length() == name.length()) { - optarg = nullptr; - return &option; - } - VERIFY(arg.length() > name.length()); - if (arg[name.length()] == '=') { - optarg = raw + name.length() + 1; - return &option; - } - } - - return nullptr; +Vector<StringView> s_args; +OptionParser s_parser; } -int OptionParser::handle_long_option() +int getopt(int argc, char* const* argv, char const* short_options) { - VERIFY(current_arg().starts_with("--"sv)); + s_args.clear_with_capacity(); + s_args.ensure_capacity(argc); + for (auto i = 1; i < argc; ++i) + s_args.append({ argv[i], strlen(argv[i]) }); - // We cannot set optopt to anything sensible for long options, so set it to 0. - optopt = 0; - - auto* option = lookup_long_option(m_argv[m_arg_index] + 2); - if (!option) { - report_error("Unrecognized option \033[1m%s\033[22m", m_argv[m_arg_index]); - return '?'; + if (optind == 0 || optreset == 1) { + s_parser.reset_state(); + optind = 1; + optreset = 0; } - // lookup_long_option() will also set optarg if the value of the option is - // specified using "--option=value" syntax. - // Figure out whether this option needs and/or has a value (also called "an - // argument", but let's not call it that to distinguish it from argv - // elements). - switch (option->has_arg) { - case no_argument: - if (optarg) { - report_error("Option \033[1m--%s\033[22m doesn't accept an argument", option->name); - return '?'; - } - m_consumed_args = 1; - break; - case optional_argument: - m_consumed_args = 1; - break; - case required_argument: - if (optarg) { - // Value specified using "--option=value" syntax. - m_consumed_args = 1; - } else if (m_arg_index + 1 < m_argc) { - // Treat the next argument as a value in "--option value" syntax. - optarg = m_argv[m_arg_index + 1]; - m_consumed_args = 2; - } else { - report_error("Missing value for option \033[1m--%s\033[22m", option->name); - return '?'; - } - break; - default: - VERIFY_NOT_REACHED(); - } + auto result = s_parser.getopt(s_args.span(), { short_options, strlen(short_options) }, {}, {}); - // Now that we've figured the value out, see about reporting this option to - // our caller. - if (option->flag) { - *option->flag = option->val; - return 0; - } - return option->val; + optind += result.consumed_args; + optarg = result.optarg_value.map([](auto x) { return const_cast<char*>(x.characters_without_null_termination()); }).value_or(optarg); + optopt = result.optopt_value.value_or(optopt); + return result.result; } -void OptionParser::shift_argv() +int getopt_long(int argc, char* const* argv, char const* short_options, const struct option* long_options, int* out_long_option_index) { - // We've just parsed an option (which perhaps has a value). - // Put the option (along with it value, if any) in front of other arguments. - VERIFY(optind <= static_cast<int>(m_arg_index)); - - if (optind == static_cast<int>(m_arg_index) || m_consumed_args == 0) { - // Nothing to do! - return; + s_args.clear_with_capacity(); + s_args.ensure_capacity(argc); + for (auto i = 1; i < argc; ++i) + s_args.append({ argv[i], strlen(argv[i]) }); + + size_t long_option_count = 0; + for (auto option = long_options; option && option->name; option += 1) + long_option_count++; + + Vector<OptionParser::Option> translated_long_options; + translated_long_options.ensure_capacity(long_option_count); + for (size_t i = 0; i < long_option_count; ++i) { + auto option = &long_options[i]; + + translated_long_options.append(OptionParser::Option { + .name = { option->name, strlen(option->name) }, + .requirement = option->has_arg == no_argument + ? AK::OptionParser::ArgumentRequirement::NoArgument + : option->has_arg == optional_argument + ? AK::OptionParser::ArgumentRequirement::HasOptionalArgument + : AK::OptionParser::ArgumentRequirement::HasRequiredArgument, + .flag = option->flag, + .val = option->val, + }); } - auto new_argv = const_cast<char**>(m_argv); - char* buffer[m_consumed_args]; - memcpy(buffer, &new_argv[m_arg_index], sizeof(char*) * m_consumed_args); - memmove(&new_argv[optind + m_consumed_args], &new_argv[optind], sizeof(char*) * (m_arg_index - optind)); - memcpy(&new_argv[optind], buffer, sizeof(char*) * m_consumed_args); -} - -bool OptionParser::find_next_option() -{ - for (m_arg_index = optind; m_arg_index < m_argc && m_argv[m_arg_index]; m_arg_index++) { - StringView arg = current_arg(); - // Anything that doesn't start with a "-" is not an option. - // As a special case, a single "-" is not an option either. - // (It's typically used by programs to refer to stdin). - if (!arg.starts_with('-') || arg == "-") { - if (m_stop_on_first_non_option) - return false; - continue; - } - - // As another special case, a "--" is not an option either, and we stop - // looking for further options if we encounter it. - if (arg == "--") - return false; - // Otherwise, we have found an option! - return true; + if (optind == 0 || optreset == 1) { + s_parser.reset_state(); + optind = 1; + optreset = 0; } - // Reached the end and still found no options. - return false; -} - -} - -int getopt(int argc, char* const* argv, char const* short_options) -{ - option dummy { nullptr, 0, nullptr, 0 }; - OptionParser parser { argc, argv, { short_options, strlen(short_options) }, &dummy }; - return parser.getopt(); -} + auto result = s_parser.getopt( + s_args.span(), + { short_options, strlen(short_options) }, + translated_long_options.span(), + out_long_option_index ? *out_long_option_index : Optional<int&>()); -int getopt_long(int argc, char* const* argv, char const* short_options, const struct option* long_options, int* out_long_option_index) -{ - OptionParser parser { argc, argv, { short_options, strlen(short_options) }, long_options, out_long_option_index }; - return parser.getopt(); + optind += result.consumed_args; + optarg = result.optarg_value.map([](auto x) { return const_cast<char*>(x.characters_without_null_termination()); }).value_or(optarg); + optopt = result.optopt_value.value_or(optopt); + return result.result; } |