524 lines
15 KiB
C++
524 lines
15 KiB
C++
#include <stdlib.h>
|
|
#include <cstdio>
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <iterator>
|
|
#include <set>
|
|
#include <sstream>
|
|
#include <utility>
|
|
#include <regex>
|
|
|
|
#include "util.h"
|
|
#include "show.h"
|
|
|
|
std::string trim_left(const std::string& s)
|
|
{
|
|
std::string result = s;
|
|
result.erase(result.begin(), std::find_if(result.begin(), result.end(), [](unsigned char ch) {
|
|
return !std::isspace(ch);
|
|
}));
|
|
return result;
|
|
}
|
|
|
|
std::string trim_right(const std::string& s)
|
|
{
|
|
std::string result = s;
|
|
result.erase(std::find_if(result.rbegin(), result.rend(), [](unsigned char ch) {
|
|
return !std::isspace(ch);
|
|
}).base(), result.end());
|
|
return result;
|
|
}
|
|
|
|
std::string trim(std::string s)
|
|
{
|
|
return trim_left(trim_right(std::move(s)));
|
|
}
|
|
|
|
std::string trim_char_left(std::string s, char remove)
|
|
{
|
|
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [&](char c) { return c != remove; }));
|
|
return s;
|
|
}
|
|
|
|
std::string trim_char_right(std::string s, char remove)
|
|
{
|
|
s.erase(std::find_if(s.rbegin(), s.rend(), [&](char c) { return c != remove; }).base(), s.end());
|
|
return s;
|
|
}
|
|
|
|
std::string trim_char(std::string s, char remove)
|
|
{
|
|
return trim_char_left(trim_char_right(std::move(s), remove), remove);
|
|
}
|
|
|
|
|
|
std::string escape_regex(const std::string& input)
|
|
{
|
|
std::string result;
|
|
result.reserve(input.length() * 2); // Reserve space for potential escapes
|
|
|
|
for (char c : input) {
|
|
// Escape special regex characters
|
|
if (std::string("\\^$.|?*+()[{}]").find(c) != std::string::npos) {
|
|
result += '\\';
|
|
}
|
|
result += c;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string string_replace(const std::string& source, const std::string& old_str, const std::string& new_str)
|
|
{
|
|
return std::regex_replace(source, std::regex(escape_regex(old_str)), new_str);
|
|
/*
|
|
std::string result { source };
|
|
auto pos = result.find(old_str);
|
|
std::string old_str_e = old_str; // escape_regex(old_str);
|
|
while (pos != std::string::npos) {
|
|
// result = result.replace(pos, old_str.size(), new_str);
|
|
result = result.replace(pos, old_str_e.size(), new_str);
|
|
// pos = result.find(old_str);
|
|
pos = result.find(old_str_e);
|
|
// std::cout << " " << result << "\n";
|
|
}
|
|
return result;
|
|
*/
|
|
}
|
|
|
|
bool contains(const std::string& str, const std::string& substr)
|
|
{
|
|
return str.find(substr) != std::string::npos;
|
|
}
|
|
|
|
bool contains(const std::vector<std::string>& strings, const std::string& element)
|
|
{
|
|
return std::find(strings.begin(), strings.end(), element) != strings.end();
|
|
}
|
|
|
|
std::string regex_escape(const std::string& s)
|
|
{
|
|
/*
|
|
regex special { R"([\$.|?*+(){})" }; // ^ is reserved
|
|
return regex_replace(s, special, "\\[&$]");
|
|
*/
|
|
std::set chars { '\\', '|', '(', ')', '{', '}', '[', ']', '$', '^' };
|
|
std::string result {};
|
|
for (char c : s) {
|
|
if (chars.find(c) != chars.end())
|
|
result += "\\";
|
|
result += c;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
strings_t regex_split(std::string s, std::regex re, bool trim_parts)
|
|
{
|
|
strings_t result = {};
|
|
if (s.size() == 0) {
|
|
return result;
|
|
}
|
|
auto it = std::sregex_token_iterator(s.begin(), s.end(), re, -1);
|
|
while (it != std::sregex_token_iterator()) {
|
|
std::string part { *it };
|
|
if (trim_parts)
|
|
part = trim(part);
|
|
result.push_back(part);
|
|
it++;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
strings_t word_split(const std::string& s)
|
|
{
|
|
return regex_split(s, std::regex("\\s+"));
|
|
}
|
|
|
|
bool is_in(std::string s, strings_t v)
|
|
{
|
|
return find(v.begin(), v.end(), s) != v.end();
|
|
}
|
|
|
|
bool is_not_in(std::string s, strings_t v)
|
|
{
|
|
return find(v.begin(), v.end(), s) == v.end();
|
|
}
|
|
|
|
strings_t find_all(std::string str, std::regex pattern, int match_group)
|
|
{
|
|
std::sregex_iterator end {};
|
|
strings_t result;
|
|
for (std::sregex_iterator p {str.begin(), str.end(), pattern}; p!= end; ++p)
|
|
result.push_back((*p)[match_group]);
|
|
return result;
|
|
}
|
|
|
|
strings_t find_all(std::string str, std::string pattern, int match_group)
|
|
{
|
|
return find_all(str, std::regex(pattern), match_group);
|
|
}
|
|
|
|
strings_t split_into_paragraphs(const std::string& s)
|
|
{
|
|
std::string t {trim(s)};
|
|
std::string marker { "_PAR_" };
|
|
t = trim(std::regex_replace(t, std::regex(R"(\n *(\n *)+)"), marker)) + marker;
|
|
//return find_all(t, regex(R"(((\s|.)*?)" + marker + ")"), 1);
|
|
return find_all(t, std::regex(R"(((\s|.)*?)_PAR_)"), 1);
|
|
|
|
}
|
|
|
|
std::string add_margin(std::string s, unsigned int margin_size)
|
|
{
|
|
auto margin = std::string(margin_size, ' ');
|
|
return trim_right(
|
|
margin + std::regex_replace(s, std::regex(R"(\n)"), '\n' + margin));
|
|
}
|
|
|
|
std::string justify_string(const std::string& s, unsigned int width=80, bool french_spacing=false)
|
|
{
|
|
strings_t words = find_all(s, R"([^\s]+)");
|
|
std::stringstream ss {};
|
|
std::stringstream line {};
|
|
for (std::string w : words) {
|
|
if (line.str().size() + w.size() + 1 > width) {
|
|
ss << line.str() << '\n';
|
|
line.str("");
|
|
}
|
|
if (not french_spacing and w[w.size()-1] == '.')
|
|
w += " ";
|
|
line << w << " ";
|
|
}
|
|
if (!line.str().empty())
|
|
ss << line.str();
|
|
std::string result = trim(ss.str());
|
|
result = std::regex_replace(result, std::regex("~"), " ");
|
|
return result;
|
|
}
|
|
|
|
std::string justify(
|
|
const std::string& input_text, unsigned int text_width, unsigned int margin_width)
|
|
{
|
|
std::string result {};
|
|
text_width -= margin_width;
|
|
for (const std::string& par : split_into_paragraphs(trim(input_text))) {
|
|
result += justify_string(par, text_width) + "\n\n";
|
|
}
|
|
if (margin_width > 0) {
|
|
result = add_margin(result, margin_width);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string join(const strings_t& ss, const std::string& separator)
|
|
{
|
|
if (ss.empty()) {
|
|
return std::string();
|
|
} else if (ss.size() == 1) {
|
|
return ss[0];
|
|
} else {
|
|
std::stringstream strm {};
|
|
std::copy(ss.begin(), ss.end() - 1,
|
|
std::ostream_iterator<std::string>(strm, separator.c_str()));
|
|
strm << ss.back();
|
|
return strm.str();
|
|
}
|
|
}
|
|
|
|
std::string join(int argc, char* argv[], const std::string& separator)
|
|
{
|
|
std::string result {};
|
|
for (int i = 0; i < argc; ++i) {
|
|
// Append directly (see argv_to_string): avoids a temporary and the same
|
|
// GCC 12 -Wrestrict false positive.
|
|
result += argv[i];
|
|
result += separator;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string argv_to_string(int argc, char* argv[])
|
|
{
|
|
if (argc == 0) {
|
|
return "";
|
|
}
|
|
std::string result = argv[0];
|
|
for (int i = 1; i < argc; ++i) {
|
|
// Append the pieces directly rather than building a `" " + string(...)`
|
|
// temporary: identical result, avoids an allocation, and sidesteps a
|
|
// GCC 12 -Wrestrict false positive on the temporary's memcpy.
|
|
result += ' ';
|
|
result += argv[i];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
std::string plural(const std::string& word, int count)
|
|
{
|
|
std::string result {word};
|
|
if (count != 1) {
|
|
if (*(word.end()-1) == 'y')
|
|
result = word.substr(0, word.size()-2) + "ies";
|
|
else
|
|
result = word + "s";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string plural(const std::string& word, const strings_t& things)
|
|
{
|
|
std::string result { word };
|
|
if (things.size() != 1) {
|
|
if (*(word.end()-1) == 'y')
|
|
result = word.substr(0, word.size()-2) + "ies";
|
|
else
|
|
result = word + "s";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string to_be(int count, bool present)
|
|
{
|
|
std::string result {};
|
|
if (count > 1) {
|
|
if (present) {
|
|
result = "are";
|
|
} else {
|
|
result = "were";
|
|
}
|
|
} else {
|
|
if (present) {
|
|
result = "is";
|
|
} else {
|
|
result = "was";
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int max_length(strings_t ss)
|
|
{
|
|
size_t result = 0;
|
|
for_each(ss.begin(), ss.end(),
|
|
[&result](const std::string& s) { result = std::max(result, s.size()); });
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
std::vector<std::string> map_key_lengths(std::map<std::string, auto> map)
|
|
{
|
|
int result = 0;
|
|
for (auto const& item: map) {
|
|
result = std::max(result, item.first.size());
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
std::map<int, int> m;
|
|
std::vector<int> key, value;
|
|
for(std::map<int,int>::iterator it = m.begin(); it != m.end(); ++it) {
|
|
key.push_back(it->first);
|
|
value.push_back(it->second);
|
|
std::cout << "Key: " << it->first << std::endl;
|
|
std::cout << "Value: " << it->second << std::endl;
|
|
}
|
|
*/
|
|
|
|
|
|
std::vector<std::pair<std::string, std::string>> environment_variables(bool allow_empty_definitions)
|
|
{
|
|
// std::cout << "read_environment:\n";
|
|
std::vector<std::pair<std::string, std::string>> result {};
|
|
extern char **environ;
|
|
for (int i = 0; environ[i]; i++) {
|
|
auto parts = regex_split(environ[i], std::regex("="), true);
|
|
if (!allow_empty_definitions && parts.size() < 2) {
|
|
throw Internal_error(
|
|
"Incorrect environment variable format:\n" + std::string(environ[i]),
|
|
Locator(), false);
|
|
}
|
|
std::string name = parts[0];
|
|
parts.erase(parts.begin());
|
|
std::string value = join(parts, "=");
|
|
// std::cout << name << sp_arrow << value << "\n";
|
|
result.push_back({name, value});
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string replace_environment_variables(std::string str)
|
|
{
|
|
if (str.find('{') == std::string::npos || str.find('}') == std::string::npos) {
|
|
return str;
|
|
}
|
|
if (str.find('{') == std::string::npos || str.find('\n') != std::string::npos) {
|
|
return str;
|
|
}
|
|
|
|
if (str.size() < 3) {
|
|
return str;
|
|
}
|
|
std::regex variable_re(R"((.*?)\{([A-Z_]+)\})");
|
|
std::sregex_iterator end {};
|
|
std::string result {};
|
|
//sregex_iterator q {};
|
|
size_t endpos = 0;
|
|
for (std::sregex_iterator p {str.begin(), str.end(), variable_re}; p!= end; ++p) {
|
|
std::smatch m = *p;
|
|
std::string prefix = m[1];
|
|
std::string var = m[2];
|
|
endpos = m.position() + m.length();
|
|
std::string envvar = get_env_var(var);
|
|
result += prefix + envvar;
|
|
}
|
|
if (endpos < str.size() - 1) {
|
|
result += str.substr(endpos);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string abbrev(const std::string& s, unsigned int max_length, bool remove_newlines)
|
|
{
|
|
std::string result {s};
|
|
if (s.size() > max_length) {
|
|
if (remove_newlines) {
|
|
result = trim(result);
|
|
result = std::regex_replace(result, std::regex("\n"), broken_bar);
|
|
}
|
|
int suffix_size = 8;
|
|
std::string ellipsis { red + "[...]" + black };
|
|
int prefix_end = max_length - suffix_size - ellipsis.size();
|
|
result = result.replace(result.begin() + prefix_end,
|
|
result.end() - suffix_size,
|
|
ellipsis);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void remove_element(std::vector<std::string>& ss, std::string removed)
|
|
{
|
|
ss.erase(std::remove_if(ss.begin(), ss.end(),
|
|
[&removed](std::string s) { return s == removed; }),
|
|
ss.end());
|
|
}
|
|
|
|
void remove_duplicates(strings_t& ss)
|
|
{
|
|
// https://en.cppreference.com/w/cpp/algorithm/unique
|
|
std::sort(ss.begin(), ss.end());
|
|
auto last = std::unique(ss.begin(), ss.end());
|
|
ss.erase(last, ss.end());
|
|
}
|
|
|
|
std::string display_string(const std::string& s, unsigned int width, bool replace_newlines)
|
|
{
|
|
std::string suffix { "..." };
|
|
std::string result { s };
|
|
if (replace_newlines)
|
|
result = std::regex_replace(result, std::regex("\n"), "/");
|
|
auto rlen = result.length();
|
|
auto slen = suffix.length();
|
|
if ((rlen > slen) and (rlen - slen) > width) {
|
|
result = result.replace(result.begin()+width, result.end(), suffix); //substr(0, width) + suffix;
|
|
}
|
|
//result = '"' + result + '"';
|
|
return result;
|
|
}
|
|
|
|
std::pair<std::string,std::string> extract_parameter_type(std::string parameter_name)
|
|
{
|
|
std::regex name_pat { R"((\w+)\.(\w+))" };
|
|
std::smatch match {};
|
|
std::string type_name = "string";
|
|
std::string name = parameter_name;
|
|
if (std::regex_match(parameter_name, match, name_pat)) {
|
|
name = match[1];
|
|
type_name = match[2];
|
|
}
|
|
return { name, type_name };
|
|
}
|
|
|
|
std::tuple<std::string, std::string, bool> regex_split_prefix(const std::regex& pattern, const std::string& text)
|
|
{
|
|
std::smatch match;
|
|
if (std::regex_search(text, match, pattern) && match.position() == 0) {
|
|
// Match found at the beginning of the string
|
|
std::string matched = match.str();
|
|
std::string remainder = text.substr(matched.length());
|
|
return { matched, remainder, true };
|
|
} else {
|
|
// No match at the beginning
|
|
return { "", text, false };
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> dlist_split(const std::string& s)
|
|
{
|
|
// If a [^\w] character surrounded by spaces exists in s, it is the delimiter.
|
|
// If not, the first space-delimited word is the delimiter.
|
|
if (s.find(" ") == std::string::npos) { // Only one element.
|
|
//std::vector<std::string> result {s};
|
|
//return result;
|
|
return {s};
|
|
} else {
|
|
std::smatch match{};
|
|
std::string elements_str{s};
|
|
std::string delimiter {};
|
|
if (std::regex_search(s, match, std::regex(R"(\s+([^\w])\s+)"))) {
|
|
delimiter = match[1];
|
|
} else {
|
|
std::string::const_iterator iter =
|
|
std::find_if(s.cbegin(), s.cend(), [](char c) { return c == ' '; });
|
|
delimiter = std::string(s.cbegin(), iter);
|
|
elements_str = std::string(iter, s.cend());
|
|
}
|
|
std::vector<std::string> elements {
|
|
regex_split(elements_str, std::regex(delimiter), true) };
|
|
return elements;
|
|
}
|
|
}
|
|
|
|
std::string get_env_var(const std::string& var) {
|
|
std::lock_guard<std::mutex> lock(env_mutex);
|
|
const char* val = getenv(var.c_str());
|
|
return val ? std::string(val) : "";
|
|
}
|
|
|
|
std::string freplace(const std::string src, std::regex pattern, std::function<std::string(std::smatch)> func)
|
|
{
|
|
std::string result {};
|
|
std::smatch match;
|
|
|
|
bool found = std::regex_search(src.begin(), src.end(), match, pattern);
|
|
auto pos = src.begin();
|
|
// int n = 0;
|
|
while (found) {
|
|
std::string part(pos, pos + match.position());
|
|
result += part;
|
|
result += func(match);
|
|
pos += match.position(0) + match.length(0);
|
|
found = std::regex_search(pos, src.end(), match, pattern);
|
|
// n++;
|
|
}
|
|
std::string part(pos, pos + match.position());
|
|
result += part;
|
|
// std::cout << "Found " << n << " matches\n";
|
|
return result;
|
|
}
|
|
|
|
std::string exec(const char* cmd)
|
|
{
|
|
std::array<char, 128> buffer;
|
|
std::string result;
|
|
FILE* pipe = popen(cmd, "r");
|
|
if (!pipe) throw std::runtime_error("popen() failed");
|
|
while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) {
|
|
result += buffer.data();
|
|
}
|
|
pclose(pipe);
|
|
return result;
|
|
}
|