Same utility as in lix's change I3caf476e59dcb7899ac5a3d83dfa3fb7ceaaabf0. Co-authored-by: eldritch horrors <pennae@lix.systems>
167 lines
5.2 KiB
C++
167 lines
5.2 KiB
C++
#include <filesystem>
|
|
#include <string>
|
|
#include <sstream>
|
|
|
|
#include "nix/util/strings-inline.hh"
|
|
#include "nix/util/os-string.hh"
|
|
#include "nix/util/error.hh"
|
|
#include "nix/util/util.hh"
|
|
|
|
namespace nix {
|
|
|
|
template std::list<std::string> tokenizeString(std::string_view s, std::string_view separators);
|
|
template StringSet tokenizeString(std::string_view s, std::string_view separators);
|
|
template std::vector<std::string> tokenizeString(std::string_view s, std::string_view separators);
|
|
|
|
template std::list<std::string> splitString(std::string_view s, std::string_view separators);
|
|
template StringSet splitString(std::string_view s, std::string_view separators);
|
|
template std::vector<std::string> splitString(std::string_view s, std::string_view separators);
|
|
|
|
template std::list<OsString>
|
|
basicSplitString(std::basic_string_view<OsChar> s, std::basic_string_view<OsChar> separators);
|
|
|
|
template std::string concatStringsSep(std::string_view, const std::list<std::string> &);
|
|
template std::string concatStringsSep(std::string_view, const StringSet &);
|
|
template std::string concatStringsSep(std::string_view, const std::vector<std::string> &);
|
|
template std::string concatStringsSep(std::string_view, const boost::container::small_vector<std::string, 64> &);
|
|
|
|
typedef std::string_view strings_2[2];
|
|
template std::string concatStringsSep(std::string_view, const strings_2 &);
|
|
typedef std::string_view strings_3[3];
|
|
template std::string concatStringsSep(std::string_view, const strings_3 &);
|
|
typedef std::string_view strings_4[4];
|
|
template std::string concatStringsSep(std::string_view, const strings_4 &);
|
|
|
|
template std::string dropEmptyInitThenConcatStringsSep(std::string_view, const std::list<std::string> &);
|
|
template std::string dropEmptyInitThenConcatStringsSep(std::string_view, const StringSet &);
|
|
template std::string dropEmptyInitThenConcatStringsSep(std::string_view, const std::vector<std::string> &);
|
|
|
|
/**
|
|
* Shell split string: split a string into shell arguments, respecting quotes and backslashes.
|
|
*
|
|
* Used for NIX_SSHOPTS handling, which previously used `tokenizeString` and was broken by
|
|
* Arguments that need to be passed to ssh with spaces in them.
|
|
*
|
|
* Read https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html for the
|
|
* POSIX shell specification, which is technically what we are implementing here.
|
|
*/
|
|
std::list<std::string> shellSplitString(std::string_view s)
|
|
{
|
|
std::list<std::string> result;
|
|
std::string current;
|
|
bool startedCurrent = false;
|
|
bool escaping = false;
|
|
|
|
auto pushCurrent = [&]() {
|
|
if (startedCurrent) {
|
|
result.push_back(current);
|
|
current.clear();
|
|
startedCurrent = false;
|
|
}
|
|
};
|
|
|
|
auto pushChar = [&](char c) {
|
|
current.push_back(c);
|
|
startedCurrent = true;
|
|
};
|
|
|
|
auto pop = [&]() {
|
|
auto c = s[0];
|
|
s.remove_prefix(1);
|
|
return c;
|
|
};
|
|
|
|
auto inDoubleQuotes = [&]() {
|
|
startedCurrent = true;
|
|
// in double quotes, escaping with backslash is only effective for $, `, ", and backslash
|
|
while (!s.empty()) {
|
|
auto c = pop();
|
|
if (escaping) {
|
|
switch (c) {
|
|
case '$':
|
|
case '`':
|
|
case '"':
|
|
case '\\':
|
|
pushChar(c);
|
|
break;
|
|
default:
|
|
pushChar('\\');
|
|
pushChar(c);
|
|
break;
|
|
}
|
|
escaping = false;
|
|
} else if (c == '\\') {
|
|
escaping = true;
|
|
} else if (c == '"') {
|
|
return;
|
|
} else {
|
|
pushChar(c);
|
|
}
|
|
}
|
|
if (s.empty()) {
|
|
throw Error("unterminated double quote");
|
|
}
|
|
};
|
|
|
|
auto inSingleQuotes = [&]() {
|
|
startedCurrent = true;
|
|
while (!s.empty()) {
|
|
auto c = pop();
|
|
if (c == '\'') {
|
|
return;
|
|
}
|
|
pushChar(c);
|
|
}
|
|
if (s.empty()) {
|
|
throw Error("unterminated single quote");
|
|
}
|
|
};
|
|
|
|
while (!s.empty()) {
|
|
auto c = pop();
|
|
if (escaping) {
|
|
pushChar(c);
|
|
escaping = false;
|
|
} else if (c == '\\') {
|
|
escaping = true;
|
|
} else if (c == ' ' || c == '\t') {
|
|
pushCurrent();
|
|
} else if (c == '"') {
|
|
inDoubleQuotes();
|
|
} else if (c == '\'') {
|
|
inSingleQuotes();
|
|
} else {
|
|
pushChar(c);
|
|
}
|
|
}
|
|
|
|
pushCurrent();
|
|
|
|
return result;
|
|
}
|
|
|
|
std::string optionalBracket(std::string_view prefix, std::string_view content, std::string_view suffix)
|
|
{
|
|
if (content.empty()) {
|
|
return "";
|
|
}
|
|
std::string result;
|
|
result.reserve(prefix.size() + content.size() + suffix.size());
|
|
result.append(prefix);
|
|
result.append(content);
|
|
result.append(suffix);
|
|
return result;
|
|
}
|
|
|
|
const char * requireCString(const std::string & s)
|
|
{
|
|
if (std::memchr(s.data(), '\0', s.size())) [[unlikely]] {
|
|
using namespace std::string_view_literals;
|
|
auto str = replaceStrings(s, "\0"sv, "␀"sv);
|
|
throw Error("string '%s' with null (\\0) bytes used where it's not allowed", str);
|
|
}
|
|
return s.c_str();
|
|
}
|
|
|
|
} // namespace nix
|