#include #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) { (void)K::log(2, name, desc, regex_pattern); Arg arg {}; arg.m_type = "req"; arg.m_name = name; arg.m_desc = get_regex_desc(desc); arg.make_regex(regex_pattern); arg.m_syntax = "<" + 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) { (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(); 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"; } void Argv::usage(const std::string& command) { std::cout << "\nUsage: " << command << " "; for (const std::string& name : m_req_names) { std::cout << "<" + name + "> "; } if (m_opt_names.size() + m_flag_names.size() > 5) { std::cout << "[]\n"; } else { for (const std::string& name : m_names) { if (m_args[name].m_type == "req") { 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 (m_args[name].m_type == "req") { continue; } usage_line(m_args[name]); } std::cout << "\n"; } void Argv::check_flags_and_options(const std::string& command, strings_t& words) { std::vector 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 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 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 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 opt_args = {}; index++; /* if (index >= words.size()) { break; } */ std::string opt_arg = words[index] + " "; index++; /* if (index >= words.size()) { break; } */ 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) { 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; pos_args = rest; } // std::cout << "Remaining words: " << words << "\n" << pos_args << "\n"; } std::map Argv::classify_arguments(int argc, char* argv[], bool full_parse) { (void)K::log(2, argc); if (argc == 1) { return {}; } std::map named_args {}; std::vector 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& 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'") { 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]; describe(); 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 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); return get(name); } 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 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::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 = ""; } std::stringstream ss {}; ss << " "<< std::setfill(' ') << std::setw(width) << m_args[name].symbol() << " : " << value; } /* if (verbose_level == 1) { std::cout << "\n"; } */ }