Files
klammertext/mac/util.cpp
Andy Kopra ef77f03584 Klammerset: the @@@klammerset construct, its search path, and const correctness
The @@@klammerset system command formally declares a klammerset — a
named, logically related group of klammer definitions — with an
operative, idempotent declaration (:requires and :files load in order
at the declaration point, relative to the declaring file). A bare
symbol given to ktext -k, kdesc --input, or :requires resolves to
x/x.k on the search path: the document's directory, then
KLAMMERTEXT_KLAMMERSETS, then KLAMMERTEXT_HOME; kdesc --klammerset
lists the available sets. sks/sks.k is the first declared klammerset,
so `-k sks` loads the SKS by name. The engine's lookup classes were
renamed *_set → *_registry to keep the two concepts apart, and the
whole C++ tree now follows standard const-correctness conventions.
tst/ gains klammerset_test.sh (18 cases).

(from dev 64b1abf23e56)
2026-07-30 23:50:07 +02:00

525 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(const std::string& s, const 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(const std::string& s, const strings_t& v)
{
return find(v.begin(), v.end(), s) != v.end();
}
bool is_not_in(const std::string& s, const strings_t& v)
{
return find(v.begin(), v.end(), s) == v.end();
}
strings_t find_all(const std::string& str, const 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(const std::string& str, const 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(const 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(const 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(const 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, const std::string& removed)
{
ss.erase(std::remove_if(ss.begin(), ss.end(),
[&removed](const 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(const 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, const std::regex& pattern,
const 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;
}