blob: 2d3fd5245065937a64ac8ed5bf838977bede4696 [file] [log] [blame]
// Copyright 2023 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/tint/utils/cli.h"
#include <algorithm>
#include <sstream>
#include <utility>
#include "src/tint/utils/hashset.h"
#include "src/tint/utils/string.h"
#include "src/tint/utils/transform.h"
namespace tint::utils::cli {
Option::~Option() = default;
void OptionSet::ShowHelp(std::ostream& s_out) {
utils::Vector<const Option*, 32> sorted_options;
for (auto* opt : options.Objects()) {
sorted_options.Push(opt);
}
sorted_options.Sort([](const Option* a, const Option* b) { return a->Name() < b->Name(); });
struct CmdInfo {
std::string left;
std::string right;
};
utils::Vector<CmdInfo, 64> cmd_infos;
for (auto* opt : sorted_options) {
{
std::stringstream left, right;
left << "--" << opt->Name();
if (auto param = opt->Parameter(); !param.empty()) {
left << " <" << param << ">";
}
right << opt->Description();
if (auto def = opt->DefaultValue(); !def.empty()) {
right << "\ndefault: " << def;
}
cmd_infos.Push({left.str(), right.str()});
}
if (auto alias = opt->Alias(); !alias.empty()) {
std::stringstream left, right;
left << "--" << alias;
right << "alias for --" << opt->Name();
cmd_infos.Push({left.str(), right.str()});
}
if (auto sn = opt->ShortName(); !sn.empty()) {
std::stringstream left, right;
left << " -" << sn;
right << "short name for --" << opt->Name();
cmd_infos.Push({left.str(), right.str()});
}
}
const size_t kMaxRightOffset = 30;
// Measure
size_t left_width = 0;
for (auto& cmd_info : cmd_infos) {
for (auto line : utils::Split(cmd_info.left, "\n")) {
if (line.length() < kMaxRightOffset) {
left_width = std::max(left_width, line.length());
}
}
}
// Print
left_width = std::min(left_width, kMaxRightOffset);
auto pad = [&](size_t n) {
while (n--) {
s_out << " ";
}
};
for (auto& cmd_info : cmd_infos) {
auto left_lines = utils::Split(cmd_info.left, "\n");
auto right_lines = utils::Split(cmd_info.right, "\n");
size_t num_lines = std::max(left_lines.Length(), right_lines.Length());
for (size_t i = 0; i < num_lines; i++) {
bool has_left = (i < left_lines.Length()) && !left_lines[i].empty();
bool has_right = (i < right_lines.Length()) && !right_lines[i].empty();
if (has_left) {
s_out << left_lines[i];
if (has_right) {
if (left_lines[i].length() > left_width) {
// Left exceeds column width.
// Insert a new line and indent to the right
s_out << std::endl;
pad(left_width);
} else {
pad(left_width - left_lines[i].length());
}
}
} else if (has_right) {
pad(left_width);
}
if (has_right) {
s_out << " " << right_lines[i];
}
s_out << std::endl;
}
}
}
Result<OptionSet::Unconsumed> OptionSet::Parse(std::ostream& s_err,
utils::VectorRef<std::string_view> arguments_raw) {
// Build a map of name to option, and set defaults
utils::Hashmap<std::string, Option*, 32> options_by_name;
for (auto* opt : options.Objects()) {
opt->SetDefault();
for (auto name : {opt->Name(), opt->Alias(), opt->ShortName()}) {
if (!name.empty() && !options_by_name.Add(name, opt)) {
s_err << "multiple options with name '" << name << "'" << std::endl;
return Failure;
}
}
}
// Canonicalize arguments by splitting '--foo=x' into '--foo' 'x'.
std::deque<std::string_view> arguments;
for (auto arg : arguments_raw) {
if (HasPrefix(arg, "-")) {
if (auto eq_idx = arg.find("="); eq_idx != std::string_view::npos) {
arguments.push_back(arg.substr(0, eq_idx));
arguments.push_back(arg.substr(eq_idx + 1));
continue;
}
}
arguments.push_back(arg);
}
utils::Hashset<Option*, 8> options_parsed;
Unconsumed unconsumed;
while (!arguments.empty()) {
auto arg = std::move(arguments.front());
arguments.pop_front();
auto name = TrimLeft(arg, [](char c) { return c == '-'; });
if (arg == name || name.length() == 0) {
unconsumed.Push(arg);
continue;
}
if (auto opt = options_by_name.Find(name)) {
if (auto err = (*opt)->Parse(arguments); !err.empty()) {
s_err << err << std::endl;
return Failure;
}
} else {
s_err << "unknown flag: " << arg << std::endl;
auto names = options_by_name.Keys();
auto alternatives =
Transform(names, [&](const std::string& s) { return std::string_view(s); });
utils::StringStream ss;
utils::SuggestAlternativeOptions opts;
opts.prefix = "--";
opts.list_possible_values = false;
SuggestAlternatives(arg, alternatives.Slice(), ss, opts);
s_err << ss.str();
return Failure;
}
}
return unconsumed;
}
} // namespace tint::utils::cli