Assembled from klammertext-dev f344a37d0f6e. The commands, the library,
the Standard Klammer Set, the reflection klammerset and the Python
binding build and run natively on Windows (MSYS2 UCRT64 gcc, python.org
Python), and both test tiers pass there. doc/install/windows_source_install.md
is the fifth install guide: a source install parallel to the Linux and
macOS ones, verified by following it on a pristine Windows 11.
Behaviour on every system: paths Klammertext prints use forward slashes;
output is LF; the command line is UTF-8 on Windows; @eval :shell is a
POSIX shell command everywhere -- on Windows it runs the sh Klammertext
ships, BusyBox for Windows (win/busybox/, GPLv2, with its licence and the
source pointer). KLAMMERTEXT_FONTS and KLAMMERTEXT_KLAMMERSETS are lists in
the system's PATH convention (';' on Windows). On Linux and macOS nothing
observable changes.
681 lines
21 KiB
C++
681 lines
21 KiB
C++
#include <unistd.h>
|
|
#include <fstream>
|
|
#include "error.h"
|
|
#include "file.h"
|
|
#include "locator.h"
|
|
#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.empty()) {
|
|
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+"));
|
|
}
|
|
|
|
std::string capitalize(std::string s)
|
|
{
|
|
// The cast through unsigned char is what makes std::toupper safe on a
|
|
// byte above 127 (negative as a char, and undefined behaviour to pass).
|
|
if (!s.empty())
|
|
s[0] = static_cast<char>(std::toupper(static_cast<unsigned char>(s[0])));
|
|
return s;
|
|
}
|
|
|
|
std::string unquote_punctuation(const std::string& s)
|
|
{
|
|
std::string result {};
|
|
result.reserve(s.size());
|
|
for (size_t i = 0; i < s.size(); i++) {
|
|
if (s[i] == '^' && i + 1 < s.size() && std::ispunct(static_cast<unsigned char>(s[i + 1]))) {
|
|
result += s[++i];
|
|
} else {
|
|
result += s[i];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string collapse_whitespace(const std::string& s)
|
|
{
|
|
std::string result {};
|
|
bool in_space = true; // leading whitespace is dropped
|
|
for (char c : s) {
|
|
if (std::isspace(static_cast<unsigned char>(c)) != 0) {
|
|
in_space = true;
|
|
} else {
|
|
if (in_space && !result.empty()) {
|
|
result += ' ';
|
|
}
|
|
in_space = false;
|
|
result += c;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool contains_fold(const std::string& haystack, const std::string& needle)
|
|
{
|
|
auto fold = [](const std::string& s) {
|
|
std::string result {};
|
|
for (char c : s) {
|
|
result += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
}
|
|
return result;
|
|
};
|
|
return fold(haystack).find(fold(needle)) != std::string::npos;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// The error-message formatting contract (2026-08-21): a message carries no
|
|
// decisions about line breaks EXCEPT by indentation.
|
|
// * a line beginning with whitespace is VERBATIM -- an example, a pattern,
|
|
// a signature, a list entry -- emitted untouched: no folding, no
|
|
// wrapping, no "~" substitution (a pattern may contain a literal ~);
|
|
// * blank lines separate blocks (runs collapse to one);
|
|
// * everything else is prose: consecutive lines fold into one paragraph
|
|
// and are wrapped to the width.
|
|
// So prose stays machine-wrapped however the source hand-wraps it, and the
|
|
// one legitimate exception is marked in the one place it cannot be missed.
|
|
// This replaced a per-call do_justify flag on the Error constructors, whose
|
|
// decision lived apart from the text it governed. Line-based by hand: no
|
|
// std::regex over the whole message (the ambiguous-alternation hazard).
|
|
std::string justify(
|
|
const std::string& input_text, unsigned int text_width, unsigned int margin_width)
|
|
{
|
|
text_width -= margin_width;
|
|
strings_t lines {};
|
|
{
|
|
const std::string text = trim(input_text);
|
|
std::string::size_type from = 0;
|
|
while (from <= text.size()) {
|
|
auto nl = text.find('\n', from);
|
|
if (nl == std::string::npos) {
|
|
lines.push_back(text.substr(from));
|
|
break;
|
|
}
|
|
lines.push_back(text.substr(from, nl - from));
|
|
from = nl + 1;
|
|
}
|
|
}
|
|
std::string result {};
|
|
strings_t prose {};
|
|
bool pending_blank = false;
|
|
auto emit = [&](const std::string& block) {
|
|
if (!result.empty()) {
|
|
result += pending_blank ? "\n\n" : "\n";
|
|
}
|
|
result += block;
|
|
pending_blank = false;
|
|
};
|
|
auto flush_prose = [&]() {
|
|
if (!prose.empty()) {
|
|
emit(justify_string(join(prose, " "), text_width));
|
|
prose.clear();
|
|
}
|
|
};
|
|
for (const std::string& line : lines) {
|
|
if (trim(line).empty()) { // block separator
|
|
flush_prose();
|
|
pending_blank = !result.empty();
|
|
} else if (line[0] == ' ' || line[0] == '\t') { // verbatim line
|
|
flush_prose();
|
|
emit(trim_right(line));
|
|
} else { // prose
|
|
prose.push_back(trim(line));
|
|
}
|
|
}
|
|
flush_prose();
|
|
result += "\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());
|
|
}
|
|
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;
|
|
// Plain, deliberately. This string is spliced into another and shown
|
|
// later, on a stream this function cannot know -- so a colour baked in
|
|
// here could reach a pipe or a file. It also fixes an arithmetic
|
|
// error that the colours caused: ellipsis.size() counted the 14
|
|
// characters of the two escape sequences as visible width, so the
|
|
// abbreviation was cut 14 characters shorter than max_length asked.
|
|
std::string ellipsis { "[...]" };
|
|
int prefix_end = max_length - suffix_size - static_cast<int>(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;
|
|
}
|
|
|
|
// Shell_command -- see util.h. On Windows the script goes to a file under
|
|
// the temp directory (mkstemp, so concurrent runs do not collide), and the
|
|
// line is ""<busybox>" sh "<script>"" -- the outer pair of quotes because
|
|
// cmd /c strips the FIRST and LAST quote of a line holding more than two.
|
|
Shell_command::Shell_command(const std::string& sh_command, [[maybe_unused]] const Locator& loc)
|
|
{
|
|
#ifdef _WIN32
|
|
fs::path busybox = fs::path(get_env_var("KLAMMERTEXT_HOME")) / "win" / "busybox" / "busybox64u.exe";
|
|
if (!fs::exists(busybox)) {
|
|
throw Environment_error(
|
|
"The shell Klammertext runs sh commands with on Windows is missing: "
|
|
+ busybox.generic_string(), loc);
|
|
}
|
|
std::string path = (fs::temp_directory_path() / "ktext_sh_XXXXXX").string();
|
|
std::vector<char> tmpl(path.begin(), path.end());
|
|
tmpl.push_back('\0');
|
|
int fd = mkstemp(tmpl.data());
|
|
if (fd == -1) {
|
|
throw Environment_error("Could not create a temporary file for a shell command", loc);
|
|
}
|
|
close(fd);
|
|
m_script = tmpl.data();
|
|
std::ofstream out(m_script, std::ios::binary);
|
|
out << sh_command << "\n";
|
|
out.close();
|
|
m_line = "\"\"" + busybox.string() + "\" sh \"" + m_script + "\"\"";
|
|
#else
|
|
m_line = sh_command;
|
|
#endif
|
|
}
|
|
|
|
Shell_command::~Shell_command()
|
|
{
|
|
if (!m_script.empty()) {
|
|
std::error_code ec {};
|
|
fs::remove(m_script, ec);
|
|
}
|
|
}
|
|
|
|
std::string exec(const char* cmd)
|
|
{
|
|
std::array<char, 128> buffer;
|
|
std::string result;
|
|
Shell_command sh_cmd(cmd, Locator::none());
|
|
FILE* pipe = popen(sh_cmd.line().c_str(), "r");
|
|
if (!pipe) throw std::runtime_error("popen() failed");
|
|
while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) {
|
|
result += buffer.data();
|
|
}
|
|
pclose(pipe);
|
|
return result;
|
|
}
|