| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <cmdline/args_parser.h> |
| |
| #include <algorithm> |
| |
| namespace cmdline { |
| |
| namespace { |
| |
| // Returns true if the argument is the special string that indicates the end |
| // of options. |
| bool IsOptionEndFlag(const char* arg) { return strcmp(arg, "--") == 0; } |
| |
| // Checks if the given argument is a short option and if it is, returns the |
| // letter. If not, returns 0. |
| char GetShortOption(const char* arg, const char** value_begin) { |
| if (arg[0] == '-' && arg[1] != 0 && arg[1] != '-') { |
| *value_begin = &arg[2]; // Arg (if any) follows immediately. |
| return arg[1]; |
| } |
| *value_begin = nullptr; |
| return 0; |
| } |
| |
| // Checks if the given argument is a long option and returns it, not including |
| // the preceding "--". If it's not an option, returns the empty string. To |
| // differentiate args consisting of only "--" and non-options, callers should |
| // call IsOptionEndFlag() before this. |
| std::string GetLongOption(const char* arg, const char** value_begin) { |
| *value_begin = nullptr; |
| if (arg[0] != '-' || arg[1] != '-') |
| return std::string(); |
| |
| // See if there's and "=<arg>" in this flag. |
| const char* equals = strchr(arg, '='); |
| if (!equals) |
| return &arg[2]; // No "=<arg>". |
| |
| // value_begin gets everything after the equals. |
| *value_begin = &equals[1]; |
| |
| // Return everything between the '--' (2 bytes) and the equals. |
| return std::string(&arg[2], equals - arg - 2); |
| } |
| |
| } // namespace |
| |
| GeneralArgsParser::GeneralArgsParser() = default; |
| GeneralArgsParser::~GeneralArgsParser() = default; |
| |
| void GeneralArgsParser::AddGeneralSwitch(const char* long_name, const char short_name, |
| const char* help, OnOffSwitchCallback on_switch, |
| OnOffSwitchCallback off_switch) { |
| Record& record = records_.emplace_back(); |
| record.long_name = long_name; |
| record.short_name = short_name; |
| record.help_text = help; |
| record.on_switch_callback = std::move(on_switch); |
| if (off_switch != nullptr) { |
| record.off_switch_callback = std::move(off_switch); |
| } |
| } |
| |
| void GeneralArgsParser::AddGeneralSwitch(const char* long_name, const char short_name, |
| const char* help, StringCallback cb) { |
| Record& record = records_.emplace_back(); |
| record.long_name = long_name; |
| record.short_name = short_name; |
| record.help_text = help; |
| record.string_callback = std::move(cb); |
| } |
| |
| std::string GeneralArgsParser::GetHelp() const { |
| std::vector<std::string> switches; |
| for (const auto& record : records_) |
| switches.push_back(record.help_text); |
| |
| std::sort(switches.begin(), switches.end()); |
| |
| std::string result; |
| for (const std::string& str : switches) { |
| result.append(str); |
| result.append("\n\n"); |
| } |
| return result; |
| } |
| |
| Status GeneralArgsParser::ParseGeneral(int argc, const char* argv[], |
| std::vector<std::string>* params) const { |
| // Expect argv[0] to be the program itself. |
| if (argc <= 1) |
| return Status::Ok(); |
| |
| int last_option_index = argc - 1; |
| for (int i = 1; i < argc; i++) { |
| // Non-null when we find the argument. |
| const char* arg_begin = nullptr; |
| bool off_switch = false; |
| |
| const Record* record = nullptr; |
| if (IsOptionEndFlag(argv[i])) { |
| // End of option indicator. |
| last_option_index = i; |
| break; |
| } else if (char c = GetShortOption(argv[i], &arg_begin)) { |
| // Single-letter option. |
| for (const Record& cur_record : records_) { |
| if (cur_record.short_name == c) { |
| record = &cur_record; |
| break; |
| } |
| } |
| } else if (auto long_opt = GetLongOption(argv[i], &arg_begin); !long_opt.empty()) { |
| // Long option. |
| for (const Record& cur_record : records_) { |
| if (cur_record.long_name == long_opt) { |
| record = &cur_record; |
| break; |
| } else if (std::string("no") + cur_record.long_name == long_opt) { |
| off_switch = true; |
| record = &cur_record; |
| break; |
| } |
| } |
| } else { |
| // Non-option. |
| last_option_index = i - 1; |
| break; |
| } |
| |
| // If we get here we should have found a record for the option. |
| if (!record) |
| return Status::Error(std::string(argv[i]) + " is not a valid option. " + |
| invalid_option_suggestion_); |
| |
| if (NeedsArg(record)) { |
| // Arguments can be already found ("-cfoo" or "--foo=bar") or they could |
| // be the following parameter. |
| if (!arg_begin || !*arg_begin) { |
| // Argument is in the next token of the command line. |
| if (i == argc - 1) |
| return Status::Error(std::string(argv[i]) + |
| " expects an argument but none was given.\n\n" + record->help_text); |
| i++; |
| arg_begin = argv[i]; |
| } |
| } else { |
| // Don't expect an arg for this switch. |
| if (arg_begin && *arg_begin) { |
| // Arg points somewhere other than the end of the string when we |
| // weren't expecting an arg. |
| return Status::Error( |
| std::string("Unexpected value for argument that doesn't take one:\n ") + argv[i] + |
| "\n\n" + record->help_text); |
| } |
| } |
| |
| // Execute the right callback. |
| Status status = Status::Ok(); |
| if (off_switch) { |
| if (record->off_switch_callback) { |
| record->off_switch_callback(); |
| } else { |
| status = Status::Error(std::string("--") + record->long_name + |
| " can only be turned on, not off.\n\n" + record->help_text); |
| } |
| } else if (record->on_switch_callback) { |
| record->on_switch_callback(); |
| } else if (record->string_callback) { |
| status = record->string_callback(arg_begin); |
| } |
| |
| if (status.has_error()) |
| return status; |
| } |
| |
| // Everything else following the parameters are the positional arguments. |
| for (int i = last_option_index + 1; i < argc; i++) |
| params->push_back(argv[i]); |
| return Status::Ok(); |
| } |
| |
| // static |
| bool GeneralArgsParser::NeedsArg(const Record* record) { return !!record->string_callback; } |
| |
| } // namespace cmdline |