A snapshot of the development tree. The substantial changes since the last one:
COMMAND OUTPUT POLICY. The three commands display text in exactly three cases,
and each owns a stream: LOGGING under "-v" greater than 0 and an ERROR before
termination go to STDERR; OUTPUT THE USER ASKED FOR goes to STDOUT. For ktext
that output is a document, so "ktext doc.kt -d | ..." is now safe -- logging
used to share the stream and land inside the document. A bare command prints
its usage and succeeds rather than failing. Colour is emitted only to a
terminal, per stream, and NO_COLOR is honoured.
"-v 1" reports every decision whose outcome you could not have read off your own
input: the klammerset that was loaded and from which file, a font's directory, a
":files" name's file, how "-o" was expanded. Higher levels are the trace.
The commands no longer warn and continue: an anomaly is an error, described with
its location. Two exceptions remain, each for a stated reason -- a condition
that is expected and temporary by design, and a judgment that is a heuristic
rather than exact.
@cond IS NOW A TRUE SPECIAL FORM, resolved at APPLICATION time rather than when
the file is read. Two consequences for a writer:
* a state variable reaches the predicate. "@@@state Flag :value true @@@
@cond *Flag* | T | F @" renders "T"; it used to see the literal "*Flag*" and
silently take the false branch. The document now behaves like a klammer
body, whose arguments are bound before its conditionals are decided.
* nothing in a discarded branch happens -- it is not read, not evaluated, not
expanded. An @eval in the branch not taken used to run anyway.
Its predicate relation is total and strict: true, True, 1; false, False, 0, and
empty; anything else is an error at the @cond rather than silently false.
@eval REACHING OUTSIDE. ":shell" and ":haskell" now keep the command's standard
error out of the document (it appears under "-v 1") and treat a nonzero exit as
an error naming what the command reported. A command that exits nonzero on
purpose -- "grep" finding no match -- says so with "|| true".
KLAMMER SETS. Several combine: "--klammersets a b c" loads all three in the
order given, sharing one namespace, with the definition modes deciding
collisions. "none" means none and may not be combined with other symbols. A
klammerset with symbol X is declared in a file X/X.k, which is what lets two
sets require the same third set without loading it twice.
TESTS. Four new suites: the kdiag command's interface, the @eval primitive's
contract with the outside world, and verbosity at both tiers. Three suites
that could not run on macOS at all now do.
Assembled from dev commit 6c8ee6c22fca.
668 lines
22 KiB
C++
668 lines
22 KiB
C++
#include <numeric>
|
|
|
|
#include "file.h"
|
|
#include "argv.h"
|
|
#include "error.h"
|
|
#include "log.h"
|
|
#include "util.h"
|
|
#include "show.h"
|
|
|
|
std::string Argv::delimiter = "--";
|
|
|
|
std::ostream& operator<<(std::ostream& os, const Arg& arg)
|
|
{
|
|
os << "<" << arg.m_type << " " << arg.m_name
|
|
<< " " << q_(arg.m_value) << ">";
|
|
return os;
|
|
}
|
|
|
|
std::string wrap_around(const std::string& text, std::size_t indent, std::size_t width=96)
|
|
{
|
|
std::string result {};
|
|
std::size_t current = indent;
|
|
std::string margin(indent, ' ');
|
|
for (const std::string& word : word_split(text)) {
|
|
if (current + 1 + word.size() > width) {
|
|
result += "\n" + margin;
|
|
current = indent;
|
|
}
|
|
result += word + " ";
|
|
current += word.size() + 1;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string flag_name(const std::string& name)
|
|
{
|
|
std::string result {};
|
|
if (name.size() == 1) {
|
|
result = "-" + name;
|
|
} else {
|
|
result = "--" + name;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string Arg::symbol()
|
|
{
|
|
std::string result = m_name;
|
|
if (m_type != "req") {
|
|
result = flag_name(m_name);
|
|
/*
|
|
if (m_name.size() == 1) {
|
|
result = "-" + m_name;
|
|
} else {
|
|
result = "--" + m_name;
|
|
}
|
|
*/
|
|
} else {
|
|
result = "<" + result + ">";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
void Arg::make_regex(const std::string& key)
|
|
{
|
|
std::string pat {};
|
|
if (regex_symbols.find(key) != regex_symbols.end()) {
|
|
pat = regex_symbols[key];
|
|
m_rgx_symbol = key;
|
|
// std::cout << "rgx_symbol: " << m_rgx_symbol << "\n";
|
|
} else if (key.find("(") != std::string::npos) {
|
|
pat = key;
|
|
}
|
|
if (pat.size() == 0) {
|
|
throw Definition_error("No regex pattern defined for \"" + key + "\".");
|
|
}
|
|
m_pattern = pat;
|
|
m_rgx = std::regex(m_pattern);
|
|
}
|
|
|
|
|
|
void Argv::update_width(Arg arg)
|
|
{
|
|
m_syntax_size = std::max(m_syntax_size, arg.m_syntax.size());
|
|
}
|
|
|
|
std::string get_regex_desc(const std::string& desc)
|
|
{
|
|
if (regex_desc.find(desc) != regex_desc.end()) {
|
|
return regex_desc[desc];
|
|
} else {
|
|
return desc;
|
|
}
|
|
}
|
|
|
|
void Argv::flag(const std::string& name, const std::string& desc)
|
|
{
|
|
(void)K::log(2, name, desc);
|
|
Arg arg {};
|
|
arg.m_type = "flag";
|
|
arg.m_name = name;
|
|
arg.m_desc = get_regex_desc(desc);
|
|
arg.m_rgx = std::regex(R"((\w+))");
|
|
arg.m_syntax = arg.symbol();
|
|
arg.m_value = "false";
|
|
m_args[name] = arg;
|
|
m_names.push_back(name);
|
|
m_flag_names.push_back(name);
|
|
m_hyphen_markers.push_back(flag_name(name));
|
|
update_width(arg);
|
|
}
|
|
|
|
void Argv::req(const std::string& name, const std::string& desc,
|
|
const std::string& regex_pattern, bool required)
|
|
{
|
|
(void)K::log(2, name, desc, regex_pattern);
|
|
Arg arg {};
|
|
// "posopt" is a positional that may be absent. It stays in m_req_names so
|
|
// it keeps its POSITION; only its absence is tolerated.
|
|
arg.m_type = required ? "req" : "posopt";
|
|
arg.m_name = name;
|
|
arg.m_desc = get_regex_desc(desc);
|
|
arg.make_regex(regex_pattern);
|
|
arg.m_syntax = required ? "<" + name + ">" : "[<" + name + ">]";
|
|
m_args[name] = arg;
|
|
m_names.push_back(name);
|
|
m_req_names.push_back(name);
|
|
update_width(arg);
|
|
}
|
|
|
|
void Argv::opt(const std::string& name, const std::string& desc, const std::string& parameter, const std::string& default_value, const std::string& regex_pattern)
|
|
{
|
|
(void)K::log(2, name, desc, parameter, default_value, regex_pattern);
|
|
Arg arg {};
|
|
arg.m_type = "opt";
|
|
arg.m_name = name;
|
|
arg.m_parameter = parameter;
|
|
arg.m_default_value = default_value;
|
|
arg.m_value = default_value;
|
|
arg.m_desc = get_regex_desc(desc);
|
|
arg.make_regex(regex_pattern);
|
|
arg.m_syntax = arg.symbol() + " <" + arg.m_parameter + ">";
|
|
m_args[name] = arg;
|
|
m_names.push_back(name);
|
|
m_opt_names.push_back(name);
|
|
m_hyphen_markers.push_back(flag_name(name));
|
|
update_width(arg);
|
|
}
|
|
|
|
void Argv::var(const std::string& name, const std::string& desc,
|
|
const std::string& parameter)
|
|
{
|
|
(void)K::log(2, name, desc);
|
|
Arg arg {};
|
|
arg.m_type = "var";
|
|
arg.m_name = name;
|
|
arg.m_desc = get_regex_desc(desc);
|
|
arg.m_syntax = arg.symbol();
|
|
if (!parameter.empty()) {
|
|
arg.m_syntax += " [<" + parameter + ">]";
|
|
}
|
|
m_args[name] = arg;
|
|
m_names.push_back(name);
|
|
m_var_names.push_back(name);
|
|
m_hyphen_markers.push_back(flag_name(name));
|
|
update_width(arg);
|
|
}
|
|
|
|
void Argv::parse_vars(strings_t& words, string_map& named_args)
|
|
{
|
|
for (const std::string& name : m_var_names) {
|
|
auto it = std::ranges::find(words, flag_name(name));
|
|
named_args[name] = "";
|
|
if (it == words.end()) {
|
|
continue;
|
|
}
|
|
m_given.insert(name);
|
|
auto first = it + 1;
|
|
auto last = first;
|
|
while (last != words.end() && !(*last).empty() && (*last)[0] != '-') {
|
|
last++;
|
|
}
|
|
named_args[name] = join(strings_t(first, last), " ");
|
|
m_vectors[name] = strings_t(first, last);
|
|
words.erase(it, last);
|
|
}
|
|
}
|
|
|
|
void Argv::usage_line(Arg arg)
|
|
{
|
|
std::cout.fill(' ');
|
|
std::cout << " " << std::left << std::setw(m_syntax_size) << arg.m_syntax << " "
|
|
<< wrap_around(arg.m_desc, m_syntax_size + 6) << "\n";
|
|
}
|
|
|
|
// A positional argument, whether or not it may be absent. Both kinds are
|
|
// listed under "Arguments:" and must therefore be skipped when the options are
|
|
// listed -- testing only for "req" printed an optional positional twice, once
|
|
// in each section (kdiag's "[<input>]").
|
|
static bool is_positional(const std::string& type)
|
|
{
|
|
return type == "req" || type == "posopt";
|
|
}
|
|
|
|
void Argv::usage(const std::string& command)
|
|
{
|
|
std::cout << "\nUsage: " << command << " ";
|
|
for (const std::string& name : m_req_names) {
|
|
std::cout << m_args[name].m_syntax << " ";
|
|
}
|
|
if (m_opt_names.size() + m_flag_names.size() > 5) {
|
|
std::cout << "[<optional-arguments>]\n";
|
|
} else {
|
|
for (const std::string& name : m_names) {
|
|
if (is_positional(m_args[name].m_type)) {
|
|
continue;
|
|
}
|
|
std::cout << "[" + m_args[name].m_syntax + "] ";
|
|
}
|
|
std::cout << "\n";
|
|
}
|
|
if (!m_req_names.empty()) {
|
|
//std::cout << "\n" << plural("Argument", m_req_names.size()) << ":\n";
|
|
std::cout << "\n" << "Arguments:\n";
|
|
for (const std::string& req_name : m_req_names) {
|
|
usage_line(m_args[req_name]);
|
|
}
|
|
}
|
|
int flag_count = m_flag_names.size();
|
|
int opt_count = m_opt_names.size();
|
|
if (flag_count > 0 || opt_count > 0) {
|
|
std::cout << "\n" << plural("Option", flag_count + opt_count) << ":\n";
|
|
}
|
|
|
|
for (const auto& name : m_names) {
|
|
if (is_positional(m_args[name].m_type)) {
|
|
continue;
|
|
}
|
|
usage_line(m_args[name]);
|
|
}
|
|
std::cout << "\n";
|
|
}
|
|
|
|
|
|
void Argv::check_flags_and_options(const std::string& command, strings_t& words)
|
|
{
|
|
std::vector<std::string> not_defined {};
|
|
for (auto word : words) {
|
|
if (word[0] == '-' && word != Argv::delimiter && !is_in(word, m_hyphen_markers)) {
|
|
not_defined.push_back(word);
|
|
}
|
|
}
|
|
if (!not_defined.empty()) {
|
|
std::stringstream ss {};
|
|
ss << "The following flags were not defined for command " << q_(command) << ":\n";
|
|
for (auto w : not_defined) {
|
|
ss << " " << w << "\n";
|
|
}
|
|
throw Argument_error(ss.str(), Locator(), false);
|
|
}
|
|
}
|
|
|
|
|
|
void Argv::parse_flags(strings_t& words, string_map& named_args)
|
|
{
|
|
std::vector<std::string> flag_args {};
|
|
for (std::string flag : m_flag_names) {
|
|
// std::cout << "Flag: " << flag << "\n";
|
|
if (is_in(flag_name(flag), words)) {
|
|
flag_args.push_back(flag);
|
|
remove_element(words, flag_name(flag));
|
|
named_args[flag] = "true";
|
|
} else {
|
|
named_args[flag] = "false";
|
|
}
|
|
}
|
|
/*
|
|
std::vector<std::string> not_defined {};
|
|
for (auto word : words) {
|
|
if (word[0] == '-' && word != Argv::delimiter) {
|
|
not_defined.push_back(word);
|
|
}
|
|
}
|
|
if (!not_defined.empty()) {
|
|
std::stringstream ss {};
|
|
ss << "The following flags were not defined for command " << q_(command) << ":\n";
|
|
for (auto w : not_defined) {
|
|
ss << " " << w << "\n";
|
|
}
|
|
throw Argument_error(ss.str(), Locator(), false);
|
|
}
|
|
*/
|
|
// std::cout << "Flags found: " << flag_args << "\n";
|
|
}
|
|
|
|
void Argv::parse_optional(strings_t& words, string_map& named_args)
|
|
{
|
|
// msg() << "parse_optional: " << words << "\n";
|
|
std::map<std::string, std::string> opt_args {};
|
|
for (std::string opt : m_opt_names) {
|
|
// std::cout << "Opt: " << opt << sp_arrow << m_args[opt].m_pattern << "\n";
|
|
auto it = std::ranges::find(words, flag_name(opt));
|
|
if (it != words.end()) {
|
|
// msg() << "words: " << words.size() << " " << words << "\n";
|
|
size_t index = std::distance(words.begin(), it);
|
|
// std::cout << " Found: " << words[index] << "\n";
|
|
//std::vector<std::string> opt_args = {};
|
|
index++;
|
|
// An option declared with opt() takes a value, so the word after
|
|
// the flag must exist. Written last with nothing after it -- a
|
|
// bare "kdesc -v", or "ktext doc.kt -t" -- this read past the end
|
|
// of the vector and the command died with SIGSEGV, naming
|
|
// nothing. (Two bounds checks used to sit here, commented out;
|
|
// they would have returned a HALF-PARSED option rather than
|
|
// reporting the mistake, so this reports it instead.) A valueless
|
|
// option is declared with flag(), not opt().
|
|
if (index >= words.size()) {
|
|
throw Argument_error(
|
|
"The option " + flag_name(opt) + " needs a value: " +
|
|
m_args[opt].m_syntax + ". Enter \"" +
|
|
file_basename(command_name) + "\" for the list of arguments.",
|
|
Locator(), false);
|
|
}
|
|
std::string opt_arg = words[index] + " ";
|
|
index++;
|
|
std::regex opt_regex = m_args[opt].m_rgx;
|
|
|
|
// std::cout << "Regex match? " << std::regex_match(trim(opt_arg), opt_regex) << "\n";
|
|
|
|
while (index < words.size() && words[index][0] != '-'
|
|
// && std::regex_match(trim(opt_arg), opt_regex)
|
|
&& std::regex_match(trim(opt_arg + words[index]), opt_regex)
|
|
|
|
) {
|
|
// opt_args.push_back(words[index++]);
|
|
opt_arg += words[index++] + " " ;
|
|
}
|
|
opt_arg = trim(opt_arg);
|
|
// std::cout << " Value: " << opt_arg << "[" << index << "]\n";
|
|
opt_args[opt] = opt_arg;
|
|
words.erase(it, words.begin() + index);
|
|
// std::cout << " Remaining: " << words << "\n";
|
|
named_args[opt] = opt_arg;
|
|
} else { // Not found
|
|
|
|
}
|
|
}
|
|
// std::cout << "opt_args:\n";
|
|
// if (opt_args.empty()) {
|
|
// std::cout << "[none]";
|
|
// } else {
|
|
// std::cout << opt_args;
|
|
// }
|
|
// std::cout << "\n";
|
|
}
|
|
|
|
void Argv::parse_positional(const std::string& command, //strings_t words,
|
|
std::string pos_args, string_map& named_args)
|
|
{
|
|
for (const std::string& req : m_req_names) {
|
|
auto arg = m_args[req];
|
|
auto [substring, rest, found] = regex_split_prefix(arg.m_rgx, pos_args);
|
|
if (!found) {
|
|
// An optional positional simply stays empty; only a required one
|
|
// is an error.
|
|
if (arg.m_type == "posopt") {
|
|
named_args[req] = "";
|
|
continue;
|
|
}
|
|
std::stringstream ss {};
|
|
ss << "The argument " << q_(req) << " was not found in:\n " << command;
|
|
throw Argument_error(ss.str(), Locator(), false);
|
|
}
|
|
named_args[req] = substring;
|
|
// Drop the whitespace that separated this positional from the next.
|
|
// regex_split_prefix() requires its match at position 0 and returns
|
|
// the remainder verbatim, so without this the SECOND required
|
|
// argument is always "not found": its pattern is offered " second"
|
|
// and cannot match a leading space.
|
|
//
|
|
// Trimming here rather than at the top of the loop is deliberate: the
|
|
// first positional still receives pos_args exactly as before, so a
|
|
// command with ONE required argument -- which is every command that
|
|
// ships (ktext's `filenames`, kdiag's `input`) -- parses
|
|
// bit-identically. Only the case that never worked changes.
|
|
size_t next = rest.find_first_not_of(" \t");
|
|
pos_args = (next == std::string::npos) ? "" : rest.substr(next);
|
|
}
|
|
// std::cout << "Remaining words: " << words << "\n" << pos_args << "\n";
|
|
}
|
|
|
|
|
|
|
|
std::map<std::string, std::string>
|
|
Argv::classify_arguments(int argc, char* argv[], bool full_parse)
|
|
{
|
|
(void)K::log(2, argc);
|
|
if (argc == 1) {
|
|
return {};
|
|
}
|
|
std::map<std::string, std::string> named_args {};
|
|
std::vector<std::string> words(argv + 1, argv + argc);
|
|
if (full_parse) {
|
|
check_flags_and_options(argv[0], words);
|
|
}
|
|
parse_vars(words, named_args);
|
|
parse_flags(words, named_args);
|
|
parse_optional(words, named_args);
|
|
parse_positional(argv_to_string(argc, argv), join(words, " "), named_args);
|
|
words.erase(std::remove(words.begin(), words.end(), Argv::delimiter), words.end());
|
|
if (m_req_names.size() == 1) {
|
|
// A single positional argument owns all remaining words; keep the
|
|
// original argv boundaries alongside the joined named_args value.
|
|
m_vectors[m_req_names[0]] = words;
|
|
}
|
|
// std::cout << "Named args:\n" << named_args << "\n";
|
|
return named_args;
|
|
}
|
|
|
|
void Argv::check_required(
|
|
const std::vector<std::string>& req_args, const std::string& command)
|
|
{
|
|
(void)K::log(2);
|
|
auto required = m_req_names.size();
|
|
auto given = req_args.size();
|
|
|
|
if (required > given) {
|
|
std::string missing = m_req_names[required - given - 1];
|
|
if (given == 0 && (m_args[missing].m_rgx_symbol == "'list'"
|
|
|| m_args[missing].m_type == "posopt")) {
|
|
return;
|
|
}
|
|
std::stringstream ss {};
|
|
ss << "The required argument \"" << missing
|
|
<< "\" was not provided in command \"" << command << "\"";
|
|
throw Argument_error(ss.str());
|
|
} else if (required < given) {
|
|
std::stringstream ss {};
|
|
ss << "Too many required arguments were given for command \""
|
|
<< command << "\" (" << required << " needed but "
|
|
<< given << " given" << ")";
|
|
throw Argument_error(ss.str());
|
|
}
|
|
}
|
|
|
|
void Argv::check_flags(const string_map& arg_map, const std::string& command)
|
|
{
|
|
(void)K::log(3);
|
|
// std::cout << "arg_map:\n" << arg_map << "\n";
|
|
|
|
strings_t undefined {};
|
|
for (const auto& pair : arg_map) {
|
|
auto [key, value] = pair;
|
|
// std::cout << "m_type: " << m_args[key].m_type << "\n";
|
|
if (key[0] != '_') {
|
|
if (m_args[key].m_type != "req"
|
|
&& !contains(m_flag_names, key)
|
|
&& !contains(m_opt_names, key)) {
|
|
undefined.push_back(key);
|
|
}
|
|
}
|
|
}
|
|
if (!undefined.empty()) {
|
|
std::stringstream ss {};
|
|
ss << "Undefined arguments given for command \"" << command << "\":";
|
|
for (const std::string& undef : undefined) {
|
|
ss << " " << flag_name(undef);
|
|
}
|
|
throw Argument_error(ss.str(), Locator());
|
|
}
|
|
}
|
|
|
|
|
|
void Argv::parse(int argc, char* argv[], bool full_parse)
|
|
{
|
|
(void)K::log(2);
|
|
command_name = argv[0];
|
|
// No describe() here. A call sat at this point, before any value has
|
|
// been assigned, so it could only ever print a table of "<none>" -- and
|
|
// because set_verbose_level() parses a throwaway Argv before the real
|
|
// one, EVERY command would print that table on every run. Silencing
|
|
// Argv::describe() itself was the wrong half of the fix: it also silenced
|
|
// the callers that legitimately want it (kdesc -v, argv_test). The
|
|
// caller decides; parse() does not print.
|
|
auto input_args = classify_arguments(argc, argv, full_parse);
|
|
// std::cout << "parse() classify:\n" << input_args << "\n";
|
|
|
|
for (auto [key, value] : input_args) {
|
|
m_args[key].m_value = value;
|
|
}
|
|
|
|
// std::cout << "m_flag_names: " << m_flag_names << "\n";
|
|
/*
|
|
for (const std::string& name : m_flag_names) {
|
|
if (input_args.find(name) != input_args.end()) {
|
|
m_args[name].m_value = "true";
|
|
}
|
|
}
|
|
*/
|
|
/*
|
|
for (const std::string& name : m_opt_names) {
|
|
if (input_args.find(name) != input_args.end()) {
|
|
std::smatch match {};
|
|
if (std::regex_match(input_args[name], match, m_args[name].m_rgx)) {
|
|
m_args[name].m_value = input_args[name];
|
|
} else {
|
|
usage(file_basename(argv[0]));
|
|
throw Argument_error(
|
|
"Incorrect value for option \"" + name + "\":\n" + m_args[name].m_desc + "\n",
|
|
Locator(), false);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
std::cout << "input_args:\n" << input_args << "\n";
|
|
if (full_parse) {
|
|
for (auto [key, value] : input_args) {
|
|
auto arg = m_args[key];
|
|
if (arg.m_type == "req") {
|
|
m_args[key].m_value = value;
|
|
}
|
|
}
|
|
*/
|
|
/*
|
|
std::vector<std::string> required_args {};
|
|
for (auto [key, value] : input_args) {
|
|
// std::cout << " full_parse: key: " << key << " value: " << value << "\n";
|
|
if (key[0] == '_' && !trim(value).empty()) {
|
|
required_args.push_back(value);
|
|
}
|
|
}
|
|
check_required(required_args, command_name);
|
|
|
|
for (unsigned int i = 0; i < required_args.size(); ++i) {
|
|
m_args[m_req_names[i]].m_value = required_args[i];
|
|
}
|
|
*/
|
|
//check_flags(input_args, command_name);
|
|
// }
|
|
|
|
// std::cout << "FINAL:\n"
|
|
// << m_args;
|
|
|
|
}
|
|
|
|
std::string Argv::get(const std::string& name, bool missing_is_error)
|
|
{
|
|
if (m_args.count(name) > 0) {
|
|
return m_args.at(name).m_value;
|
|
} else if (missing_is_error) {
|
|
throw Argument_error("Command-line argument \"" + name + "\" is not defined");
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
bool Argv::as_bool(const std::string& name)
|
|
{
|
|
(void)K::log(2, name);
|
|
return get(name) == "true";
|
|
}
|
|
|
|
int Argv::as_int(const std::string& name)
|
|
{
|
|
(void)K::log(2, name);
|
|
auto value = get(name);
|
|
if (!std::regex_match(value, std::regex(R"([-+]?\d+)"))) {
|
|
throw Argument_error("Argument \"" + value + "\" is not an integer");
|
|
}
|
|
return std::stoi(value);
|
|
}
|
|
|
|
int Argv::as_integer_range(const std::string& name, int low, int high)
|
|
{
|
|
(void)K::log(2, name);
|
|
auto value = get(name);
|
|
std::stringstream ss {};
|
|
ss << "Argument \"" << value << "\" is not an integer in the range of "
|
|
<< low << " to " << high;
|
|
if (!std::regex_match(value, std::regex(R"([-+]?\d+)"))) {
|
|
throw Argument_error(ss.str());
|
|
}
|
|
int result = std::stoi(value);
|
|
if (result < low || result > high) {
|
|
throw Argument_error(ss.str());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int Argv::as_verbosity(const std::string& name)
|
|
{
|
|
(void)K::log(2, name);
|
|
auto value = get(name);
|
|
if (!std::regex_match(value, std::regex(regex_symbols["'verbosity'"]))) {
|
|
throw Argument_error("Argument \"" + value + "\" is not a verbosity level");
|
|
}
|
|
return std::stoi(value);
|
|
}
|
|
|
|
std::string Argv::as_string(const std::string& name)
|
|
{
|
|
(void)K::log(2, name);
|
|
auto result = get(name);
|
|
return result;
|
|
}
|
|
|
|
strings_t Argv::as_vector(const std::string& name)
|
|
{
|
|
(void)K::log(2, name);
|
|
if (m_vectors.count(name) > 0) {
|
|
return m_vectors.at(name);
|
|
}
|
|
// No stored boundaries (e.g. an opt, whose value is a single argv
|
|
// word): the value is one element, spaces and all -- never re-split.
|
|
std::string value = get(name);
|
|
if (value.empty()) {
|
|
return {};
|
|
}
|
|
return { value };
|
|
}
|
|
|
|
std::pair<std::string, strings_t> Argv::as_input(const std::string& name, bool allow_empty)
|
|
{
|
|
(void)K::log(2, name);
|
|
const std::string& input_arg = get(name);
|
|
std::regex filename_rgx(R"(([./\w]+\.kt?))");
|
|
strings_t input_filenames = find_all(input_arg, filename_rgx, 1);
|
|
const std::string& input_text = trim(std::regex_replace(input_arg, filename_rgx, ""));
|
|
if (input_text.empty() && input_filenames.empty()) {
|
|
if (allow_empty) {
|
|
return {{},{}};
|
|
} else {
|
|
throw Argument_error("No input text or filenames specified");
|
|
}
|
|
}
|
|
(void)K::log(2, "Input text", input_text, 1);
|
|
for (const auto& f : input_filenames) {
|
|
(void)K::log(2, "Input filename", f);
|
|
}
|
|
return { input_text, input_filenames };
|
|
}
|
|
|
|
void Argv::describe(std::ostream& os)
|
|
{
|
|
std::size_t width = std::accumulate(
|
|
m_names.begin(), m_names.end(), 0,
|
|
[&] (size_t w, const std::string& name) {
|
|
return std::max(w, m_args[name].symbol().size()); });
|
|
|
|
for (const std::string& name : m_names) {
|
|
std::string value = m_args[name].m_value;
|
|
if (value.empty()) {
|
|
value = "<none>";
|
|
}
|
|
std::stringstream ss {};
|
|
ss << " "<< std::setfill(' ') << std::setw(width)
|
|
<< m_args[name].symbol() << " : " << value;
|
|
// The line was built and then dropped, so this printed nothing at
|
|
// all -- which is why "kdesc -v 1" showed no arguments and argv_test,
|
|
// whose whole job is to display what Argv parsed, was silent. The
|
|
// callers already decide whether to call it (the commands gate it on
|
|
// verbose_level > 0), so it prints unconditionally here.
|
|
os << ss.str() << "\n";
|
|
}
|
|
}
|