blob: a6998a48754f4b9f8995934406edd63b145cd3b4 [file] [log] [blame]
// Copyright 2010 The Goma 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 "lib/flag_parser.h"
#include <algorithm>
#include <iterator>
#include <utility>
#include "absl/strings/match.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/string_view.h"
#include "glog/logging.h"
using std::string;
namespace {
struct FlagLengthComparator {
bool operator() (FlagParser::Flag* a, FlagParser::Flag* b) const {
return a->name().size() > b->name().size();
}
};
} // anonymous namespace
FlagParser::Options::Options()
: flag_prefix('-'),
alt_flag_prefix('\0'),
allows_equal_arg(false),
allows_nonspace_arg(false),
has_command_name(true) {
}
FlagParser::Flag::Flag(const char* name,
bool require_value,
bool allows_space_arg,
const FlagParser::Options& options)
: name_(name),
require_value_(require_value),
flag_prefix_(options.flag_prefix),
alt_flag_prefix_(options.alt_flag_prefix),
allows_equal_arg_(options.allows_equal_arg),
allows_nonspace_arg_(options.allows_nonspace_arg),
allows_space_arg_(allows_space_arg),
seen_(false),
seen_output_(nullptr),
output_(nullptr),
value_callback_(nullptr),
values_output_(nullptr),
parse_callback_(nullptr) {
}
FlagParser::Flag::~Flag() {
}
void FlagParser::Flag::SetSeenOutput(bool* seen_output) {
seen_output_ = seen_output;
*seen_output_ = false;
}
void FlagParser::Flag::SetOutput(std::vector<string>* output) {
output_ = output;
}
void FlagParser::Flag::SetValueOutputWithCallback(
Callback* callback, std::vector<string>* values) {
value_callback_ = callback;
values_output_ = values;
}
void FlagParser::Flag::SetCallbackForParsedArgs(Callback* callback) {
parse_callback_ = callback;
}
bool FlagParser::Flag::Parse(const std::vector<string>& args, size_t i,
size_t* last_i) {
absl::string_view key;
if (!flag_prefix_) {
key = args[i];
} else if (args[i].size() > 1 &&
(args[i][0] == flag_prefix_
|| (alt_flag_prefix_ && args[i][0] == alt_flag_prefix_))) {
key = absl::ClippedSubstr(absl::string_view(args[i]), 1);
} else {
// non flag args
VLOG(3) << "non flag arg:" << args[i];
}
VLOG(4) << "check flag '" << key << "' by '" << name_ << "'";
if (name_.empty()) {
if (key.empty()) {
VLOG(3) << "FlagParser: non flag: " << args[i];
Output(i, args[i], &args[i]);
*last_i = i;
return true;
}
if (args[i][0] != flag_prefix_) {
VLOG(3) << "FlagParser: maybe non flag? " << args[i];
Output(i, args[i], &args[i]);
*last_i = i;
return true;
}
return false;
}
if (!absl::StartsWith(key, name_)) {
return false;
}
if (key == name_) {
if (!require_value_) {
// E.g., "-c"
VLOG(3) << "FlagParser: no require value: " << key;
Output(i, args[i], nullptr);
*last_i = i;
return true;
}
if (!allows_space_arg_) {
// E.g., "-O"
VLOG(3) << "FlagParser: no allow space arg: " << key;
string no_value;
Output(i, args[i], &no_value);
*last_i = i;
return true;
}
// E.g., "-x c++"
if (i + 1U == args.size()) {
VLOG(2) << "FlagParser: " << args[i] << " should take an argument";
return false;
}
VLOG(3) << "FlagParser: key-value argument with space: " << args[i];
Output(i, args[i], nullptr);
Output(i + 1, args[i + 1], &args[i + 1]);
*last_i = i + 1;
return true;
}
if (!require_value_) {
// e.g. -clang-syntax for -c.
return false;
}
if (allows_equal_arg_) {
size_t equal_index = key.find('=');
if (equal_index != string::npos &&
key.substr(0, equal_index) == name_) {
// E.g., "-isysroot=/foobar"
VLOG(3) << "FlagParser: key-value argument with equal: " << args[i];
const string value = string(absl::ClippedSubstr(key, equal_index + 1));
Output(i, args[i], &value);
*last_i = i;
return true;
}
}
if (allows_nonspace_arg_) {
// E.g. "-xc++" or "-O2"
VLOG(3) << "FlagParser: key-value argument without separator: " << args[i];
const string value =
args[i].substr(name_.size() + (flag_prefix_ ? 1 : 0));
Output(i, args[i], &value);
*last_i = i;
return true;
}
return false;
}
const string& FlagParser::Flag::value(int i) const {
CHECK_GE(i, 0);
CHECK_LT(i, static_cast<int>(values_.size()));
return values_[i];
}
string FlagParser::Flag::GetLastValue() const {
if (values_.empty())
return "";
return values_[values_.size() - 1];
}
const string& FlagParser::Flag::GetParsedArgs(int i) const {
auto found = parsed_args_.find(i);
CHECK(found != parsed_args_.end()) << name_ << " at " << i;
return found->second;
}
void FlagParser::Flag::Output(int i, const string& arg, const string* value) {
VLOG(4) << "Output:" << i << " " << arg << " value="
<< (value ? *value : "(null)");
seen_ = true;
if (seen_output_)
*seen_output_ = true;
if (output_)
output_->push_back(arg);
if (value == nullptr) {
CHECK(parsed_args_.insert(std::make_pair(i, arg)).second);
return;
}
values_.push_back(*value);
if (values_output_ != nullptr) {
string v;
if (value_callback_)
v = value_callback_->ParseFlagValue(*this, *value);
else
v = *value;
values_output_->push_back(v);
}
string parsed_value = *value;
if (parse_callback_)
parsed_value = parse_callback_->ParseFlagValue(*this, *value);
string parsed_arg = arg;
if (parsed_value != *value) {
parsed_arg = absl::StrReplaceAll(arg, {{*value, parsed_value}});
}
CHECK(parsed_args_.insert(std::make_pair(i, parsed_arg)).second);
}
FlagParser::FlagParser() {
}
FlagParser::~FlagParser() {
}
FlagParser::Flag* FlagParser::AddBoolFlag(const char* name) {
std::pair<std::map<string, std::unique_ptr<Flag>>::iterator, bool> p =
flags_.emplace(name, nullptr);
if (p.second) {
p.first->second.reset(new Flag(name, false, false, opts_));
}
return p.first->second.get();
}
FlagParser::Flag* FlagParser::AddPrefixFlag(const char* name) {
std::pair<std::map<string, std::unique_ptr<Flag>>::iterator, bool> p =
flags_.emplace(name, nullptr);
if (p.second) {
p.first->second.reset(new Flag(name, true, false, opts_));
}
return p.first->second.get();
}
FlagParser::Flag* FlagParser::AddFlag(const char* name) {
std::pair<std::map<string, std::unique_ptr<Flag>>::iterator, bool> p =
flags_.emplace(name, nullptr);
if (p.second) {
p.first->second.reset(new Flag(name, true, true, opts_));
}
return p.first->second.get();
}
FlagParser::Flag* FlagParser::AddNonFlag() {
std::pair<std::map<string, std::unique_ptr<Flag>>::iterator, bool> p =
flags_.emplace("", nullptr);
if (p.second) {
p.first->second.reset(new Flag("", true, false, opts_));
}
return p.first->second.get();
}
void FlagParser::Parse(const std::vector<string>& args) {
std::copy(args.begin(), args.end(), back_inserter(args_));
parsed_flags_.resize(args_.size());
// Check longest flag name first.
std::vector<Flag*> flags;
for (const auto& iter : flags_) {
flags.push_back(iter.second.get());
}
FlagLengthComparator comp;
std::sort(flags.begin(), flags.end(), comp);
for (size_t i = opts_.has_command_name ? 1 : 0; i < args.size(); i++) {
const string& arg = args[i];
VLOG(4) << "FlagParser: arg=" << arg;
if (arg.empty()) {
VLOG(3) << "FlagParser: empty flag";
continue;
}
bool parsed = false;
for (size_t j = 0; j < flags.size(); ++j) {
size_t last_i;
if (flags[j]->Parse(args_, i, &last_i)) {
VLOG(3) << "matched for flag '" << flags[j]->name() << "' for "
<< args_[i];
for (; i <= last_i; i++)
parsed_flags_[i] = flags[j];
i = last_i;
parsed = true;
break;
}
}
if (!parsed && arg.front() == opts_.flag_prefix) {
unknown_flag_args_.push_back(arg);
}
}
}
std::vector<string> FlagParser::GetParsedArgs() {
std::vector<string> args;
if (opts_.has_command_name)
args.push_back(args_[0]);
for (size_t i = (opts_.has_command_name ? 1 : 0);
i < parsed_flags_.size();
++i) {
if (parsed_flags_[i])
args.push_back(parsed_flags_[i]->GetParsedArgs(i));
else
args.push_back(args_[i]);
}
return args;
}