blob: 731db6ff62e626dfd6835454cba3290f13206351 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 "cli/cli_args.h"
#include <cerrno>
#include <cstdlib>
#include <sstream>
namespace tsfile_cli {
namespace {
std::vector<std::string> split_csv(const std::string& s) {
std::vector<std::string> out;
std::string item;
std::istringstream iss(s);
while (std::getline(iss, item, ',')) {
if (!item.empty()) {
out.push_back(item);
}
}
return out;
}
bool parse_ll(const std::string& s, long long& out) {
if (s.empty()) {
return false;
}
char* endp = nullptr;
errno = 0;
long long v = std::strtoll(s.c_str(), &endp, 10);
if (endp == nullptr || *endp != '\0' || errno == ERANGE) {
return false;
}
out = v;
return true;
}
bool parse_format(const std::string& s, ParsedArgs::Format& out) {
if (s == "csv") {
out = ParsedArgs::Format::kCsv;
} else if (s == "tsv") {
out = ParsedArgs::Format::kTsv;
} else if (s == "json") {
out = ParsedArgs::Format::kJson;
} else if (s == "table") {
out = ParsedArgs::Format::kTable;
} else {
return false;
}
return true;
}
bool parse_tag_filter_op(const std::string& s, ParsedArgs::TagFilterOp& out) {
if (s == "eq" || s == "=" || s == "==") {
out = ParsedArgs::TagFilterOp::kEq;
} else if (s == "neq" || s == "ne" || s == "!=") {
out = ParsedArgs::TagFilterOp::kNeq;
} else if (s == "lt" || s == "<") {
out = ParsedArgs::TagFilterOp::kLt;
} else if (s == "lteq" || s == "lte" || s == "le" || s == "<=") {
out = ParsedArgs::TagFilterOp::kLteq;
} else if (s == "gt" || s == ">") {
out = ParsedArgs::TagFilterOp::kGt;
} else if (s == "gteq" || s == "gte" || s == "ge" || s == ">=") {
out = ParsedArgs::TagFilterOp::kGteq;
} else if (s == "regexp" || s == "regex" || s == "=~") {
out = ParsedArgs::TagFilterOp::kRegexp;
} else if (s == "not-regexp" || s == "not-regex" || s == "!~") {
out = ParsedArgs::TagFilterOp::kNotRegexp;
} else {
return false;
}
return true;
}
} // namespace
ParsedArgs parse_args(const std::vector<std::string>& args) {
ParsedArgs p;
if (args.empty()) {
return p;
}
p.command = args[0];
if (p.command == "--version") {
p.version = true;
}
if (p.command == "--help" || p.command == "-h") {
p.help = true;
}
// The subcommand must come first. A leading option means it was omitted;
// say so explicitly instead of failing later with a confusing message about
// the first real positional argument.
if (p.command.size() > 1 && p.command[0] == '-' && !p.version && !p.help) {
p.error = "the command must come before options (got option '" +
p.command + "'); run with --help for usage";
return p;
}
size_t i = 1;
auto need_value = [&](const std::string& flag, std::string& dst) -> bool {
if (i + 1 >= args.size()) {
p.error = "Missing value for " + flag;
return false;
}
dst = args[++i];
return true;
};
auto need_tag_filter_slot = [&](const std::string& flag) -> bool {
if (p.has_tag_filter) {
p.error = "Only one tag filter predicate is supported";
return false;
}
if (i + 3 >= args.size()) {
p.error = "Missing value for " + flag;
return false;
}
return true;
};
for (; i < args.size(); ++i) {
const std::string& a = args[i];
std::string val;
if (a == "-f" || a == "--format") {
if (!need_value(a, val)) {
return p;
}
if (!parse_format(val, p.format)) {
p.error =
"Invalid format: " + val + " (use csv|tsv|json|table)";
return p;
}
} else if (a == "-d" || a == "--device") {
if (!need_value(a, p.device)) {
return p;
}
} else if (a == "-t" || a == "--table") {
if (!need_value(a, p.table)) {
return p;
}
} else if (a == "-m" || a == "--measurements") {
if (!need_value(a, val)) {
return p;
}
p.measurements = split_csv(val);
} else if (a == "-n" || a == "--limit") {
if (!need_value(a, val)) {
return p;
}
if (!parse_ll(val, p.limit)) {
p.error = "Invalid -n/--limit: " + val;
return p;
}
} else if (a == "--offset") {
if (!need_value(a, val)) {
return p;
}
if (!parse_ll(val, p.offset)) {
p.error = "Invalid --offset: " + val;
return p;
}
} else if (a == "--start") {
if (!need_value(a, val)) {
return p;
}
if (!parse_ll(val, p.start)) {
p.error = "Invalid --start: " + val;
return p;
}
p.has_start = true;
} else if (a == "--end") {
if (!need_value(a, val)) {
return p;
}
if (!parse_ll(val, p.end)) {
p.error = "Invalid --end: " + val;
return p;
}
p.has_end = true;
} else if (a == "--seed") {
if (!need_value(a, val)) {
return p;
}
if (!parse_ll(val, p.seed)) {
p.error = "Invalid --seed: " + val;
return p;
}
p.has_seed = true;
} else if (a == "-o" || a == "--output") {
if (!need_value(a, p.output)) {
return p;
}
} else if (a == "--columns") {
if (!need_value(a, p.columns)) {
return p;
}
} else if (a == "-v" || a == "--verbose") {
p.verbose = true;
} else if (a == "--header-match") {
p.header_match = true;
} else if (a == "--tag-filter") {
if (!need_tag_filter_slot(a)) {
return p;
}
p.has_tag_filter = true;
p.tag_filter_column = args[++i];
std::string op = args[++i];
if (!parse_tag_filter_op(op, p.tag_filter_op)) {
p.error = "Invalid --tag-filter operator: " + op +
" (use eq|neq|lt|lteq|gt|gteq|regexp|not-regexp)";
return p;
}
p.tag_filter_value = args[++i];
} else if (a == "--tag-between" || a == "--tag-not-between") {
if (!need_tag_filter_slot(a)) {
return p;
}
p.has_tag_filter = true;
p.tag_filter_op = (a == "--tag-between")
? ParsedArgs::TagFilterOp::kBetween
: ParsedArgs::TagFilterOp::kNotBetween;
p.tag_filter_column = args[++i];
p.tag_filter_value = args[++i];
p.tag_filter_value2 = args[++i];
} else if (a == "--model") {
if (!need_value(a, val)) {
return p;
}
if (val != "tree" && val != "table") {
p.error = "Invalid --model: " + val + " (use tree|table)";
return p;
}
p.model = val;
} else if (a == "--no-header") {
p.no_header = true;
} else if (a == "-h" || a == "--help") {
p.help = true;
return p; // help wins; stop parsing the rest
} else if (a == "--version") {
p.version = true;
return p; // version wins; stop parsing the rest
} else if (a.size() > 1 && a[0] == '-') {
p.error = "Unknown flag: " + a;
return p;
} else {
if (p.file.empty()) {
p.file = a;
} else {
p.error = "Unexpected argument: " + a;
return p;
}
}
}
return p;
}
} // namespace tsfile_cli