From b91055112035c256fffd44d77f746b977cfdf3ca Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Tue, 28 May 2024 14:57:20 +0200 Subject: [PATCH] util.{hh,cc}: Split out strings.{hh,cc} Change-Id: I4f642d1046d56b5db26f1b0296ee16a0e02d444a --- src/libstore/crypto.cc | 2 +- src/libstore/machines.cc | 2 +- src/libstore/names.cc | 2 +- src/libstore/outputs-spec.cc | 3 +- src/libstore/ssh.cc | 3 +- src/libutil/cgroup.cc | 4 +- src/libutil/config.cc | 1 + src/libutil/current-process.cc | 1 + src/libutil/experimental-features.cc | 1 + src/libutil/meson.build | 2 + src/libutil/namespaces.cc | 2 +- src/libutil/regex-combinators.hh | 2 + src/libutil/serialise.hh | 1 + src/libutil/shlex.cc | 2 +- src/libutil/strings.cc | 232 ++++++++++++++++ src/libutil/strings.hh | 256 ++++++++++++++++++ src/libutil/unix-domain-socket.cc | 2 +- src/libutil/url.cc | 2 +- src/libutil/util.cc | 230 +--------------- src/libutil/util.hh | 248 ----------------- .../repl_characterization.cc | 1 + .../repl_characterization/test-session.cc | 2 +- tests/unit/libstore/machines.cc | 1 + .../tests/cli-literate-parser.cc | 1 + tests/unit/libutil/tests.cc | 3 +- 25 files changed, 517 insertions(+), 489 deletions(-) create mode 100644 src/libutil/strings.cc create mode 100644 src/libutil/strings.hh diff --git a/src/libstore/crypto.cc b/src/libstore/crypto.cc index 1e29a812b..2e0fd8ca5 100644 --- a/src/libstore/crypto.cc +++ b/src/libstore/crypto.cc @@ -1,7 +1,7 @@ #include "crypto.hh" -#include "util.hh" #include "file-system.hh" #include "globals.hh" +#include "strings.hh" #include diff --git a/src/libstore/machines.cc b/src/libstore/machines.cc index 700c9b3dd..cdd1e1c2c 100644 --- a/src/libstore/machines.cc +++ b/src/libstore/machines.cc @@ -1,7 +1,7 @@ #include "machines.hh" -#include "util.hh" #include "globals.hh" #include "store-api.hh" +#include "strings.hh" #include diff --git a/src/libstore/names.cc b/src/libstore/names.cc index 277aabf0f..b903a99f0 100644 --- a/src/libstore/names.cc +++ b/src/libstore/names.cc @@ -1,5 +1,5 @@ #include "names.hh" -#include "util.hh" +#include "strings.hh" #include diff --git a/src/libstore/outputs-spec.cc b/src/libstore/outputs-spec.cc index 21c069223..4422bcd21 100644 --- a/src/libstore/outputs-spec.cc +++ b/src/libstore/outputs-spec.cc @@ -1,10 +1,11 @@ #include #include -#include "util.hh" #include "regex-combinators.hh" #include "outputs-spec.hh" #include "path-regex.hh" +#include "strings.hh" +#include "util.hh" namespace nix { diff --git a/src/libstore/ssh.cc b/src/libstore/ssh.cc index ccf0aef7f..932ebaa52 100644 --- a/src/libstore/ssh.cc +++ b/src/libstore/ssh.cc @@ -2,7 +2,8 @@ #include "environment-variables.hh" #include "ssh.hh" #include "finally.hh" -#include "util.hh" +#include "logging.hh" +#include "strings.hh" namespace nix { diff --git a/src/libutil/cgroup.cc b/src/libutil/cgroup.cc index aa7802f79..e28e21c3e 100644 --- a/src/libutil/cgroup.cc +++ b/src/libutil/cgroup.cc @@ -1,15 +1,17 @@ +#include "logging.hh" #if __linux__ #include "cgroup.hh" -#include "util.hh" #include "file-system.hh" #include "finally.hh" +#include "strings.hh" #include #include #include #include #include +#include #include #include diff --git a/src/libutil/config.cc b/src/libutil/config.cc index f6f14878a..729b4e596 100644 --- a/src/libutil/config.cc +++ b/src/libutil/config.cc @@ -3,6 +3,7 @@ #include "abstract-setting-to-json.hh" #include "experimental-features.hh" #include "file-system.hh" +#include "strings.hh" #include "config-impl.hh" diff --git a/src/libutil/current-process.cc b/src/libutil/current-process.cc index 826e25547..41f591b0c 100644 --- a/src/libutil/current-process.cc +++ b/src/libutil/current-process.cc @@ -3,6 +3,7 @@ #include "logging.hh" #include "signals.hh" #include "util.hh" +#include "strings.hh" #ifdef __APPLE__ # include diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc index 25beba467..8ebec2956 100644 --- a/src/libutil/experimental-features.cc +++ b/src/libutil/experimental-features.cc @@ -1,6 +1,7 @@ #include "experimental-features.hh" // Required for instances of to_json and from_json for ExperimentalFeature #include "experimental-features-json.hh" +#include "strings.hh" #include "util.hh" #include "nlohmann/json.hpp" diff --git a/src/libutil/meson.build b/src/libutil/meson.build index 73c520116..b662ea455 100644 --- a/src/libutil/meson.build +++ b/src/libutil/meson.build @@ -31,6 +31,7 @@ libutil_sources = files( 'shlex.cc', 'signals.cc', 'source-path.cc', + 'strings.cc', 'suggestions.cc', 'tarfile.cc', 'terminal.cc', @@ -96,6 +97,7 @@ libutil_headers = files( 'signals.hh', 'source-path.hh', 'split.hh', + 'strings.hh', 'suggestions.hh', 'sync.hh', 'tarfile.hh', diff --git a/src/libutil/namespaces.cc b/src/libutil/namespaces.cc index cde442671..d092e6fcc 100644 --- a/src/libutil/namespaces.cc +++ b/src/libutil/namespaces.cc @@ -2,9 +2,9 @@ #include "file-system.hh" #include "logging.hh" -#include "util.hh" #include "namespaces.hh" #include "processes.hh" +#include "strings.hh" #include diff --git a/src/libutil/regex-combinators.hh b/src/libutil/regex-combinators.hh index 87d6aa678..37962944e 100644 --- a/src/libutil/regex-combinators.hh +++ b/src/libutil/regex-combinators.hh @@ -1,6 +1,8 @@ #pragma once ///@file +#include "strings.hh" + #include namespace nix::regex { diff --git a/src/libutil/serialise.hh b/src/libutil/serialise.hh index d4a14bbf0..e46c5624a 100644 --- a/src/libutil/serialise.hh +++ b/src/libutil/serialise.hh @@ -3,6 +3,7 @@ #include +#include "strings.hh" #include "types.hh" #include "util.hh" #include "file-descriptor.hh" diff --git a/src/libutil/shlex.cc b/src/libutil/shlex.cc index b5f340251..21fa0502a 100644 --- a/src/libutil/shlex.cc +++ b/src/libutil/shlex.cc @@ -1,5 +1,5 @@ #include "shlex.hh" -#include "util.hh" +#include "strings.hh" namespace nix { diff --git a/src/libutil/strings.cc b/src/libutil/strings.cc new file mode 100644 index 000000000..9cb319cce --- /dev/null +++ b/src/libutil/strings.cc @@ -0,0 +1,232 @@ +#include "strings.hh" + +namespace nix { + +std::vector stringsToCharPtrs(const Strings & ss) +{ + std::vector res; + for (auto & s : ss) res.push_back((char *) s.c_str()); + res.push_back(0); + return res; +} + + +template C tokenizeString(std::string_view s, std::string_view separators) +{ + C result; + auto pos = s.find_first_not_of(separators, 0); + while (pos != std::string_view::npos) { + auto end = s.find_first_of(separators, pos + 1); + if (end == std::string_view::npos) end = s.size(); + result.insert(result.end(), std::string(s, pos, end - pos)); + pos = s.find_first_not_of(separators, end); + } + return result; +} + +template Strings tokenizeString(std::string_view s, std::string_view separators); +template StringSet tokenizeString(std::string_view s, std::string_view separators); +template std::vector tokenizeString(std::string_view s, std::string_view separators); + + +std::string chomp(std::string_view s) +{ + size_t i = s.find_last_not_of(" \n\r\t"); + return i == std::string_view::npos ? "" : std::string(s, 0, i + 1); +} + + +std::string trim(std::string_view s, std::string_view whitespace) +{ + auto i = s.find_first_not_of(whitespace); + if (i == s.npos) return ""; + auto j = s.find_last_not_of(whitespace); + return std::string(s, i, j == s.npos ? j : j - i + 1); +} + + +std::string replaceStrings( + std::string res, + std::string_view from, + std::string_view to) +{ + if (from.empty()) return res; + size_t pos = 0; + while ((pos = res.find(from, pos)) != std::string::npos) { + res.replace(pos, from.size(), to); + pos += to.size(); + } + return res; +} + + +Rewriter::Rewriter(std::map rewrites) + : rewrites(std::move(rewrites)) +{ + for (const auto & [k, v] : this->rewrites) { + assert(!k.empty()); + initials.push_back(k[0]); + } + std::ranges::sort(initials); + auto [firstDupe, end] = std::ranges::unique(initials); + initials.erase(firstDupe, end); +} + +std::string Rewriter::operator()(std::string s) +{ + size_t j = 0; + while ((j = s.find_first_of(initials, j)) != std::string::npos) { + size_t skip = 1; + for (auto & [from, to] : rewrites) { + if (s.compare(j, from.size(), from) == 0) { + s.replace(j, from.size(), to); + skip = to.size(); + break; + } + } + j += skip; + } + return s; +} + + +std::string toLower(const std::string & s) +{ + std::string r(s); + for (auto & c : r) + c = std::tolower(c); + return r; +} + + +std::string shellEscape(const std::string_view s) +{ + std::string r; + r.reserve(s.size() + 2); + r += "'"; + for (auto & i : s) + if (i == '\'') r += "'\\''"; else r += i; + r += '\''; + return r; +} + +constexpr char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +std::string base64Encode(std::string_view s) +{ + std::string res; + res.reserve((s.size() + 2) / 3 * 4); + int data = 0, nbits = 0; + + for (char c : s) { + data = data << 8 | (unsigned char) c; + nbits += 8; + while (nbits >= 6) { + nbits -= 6; + res.push_back(base64Chars[data >> nbits & 0x3f]); + } + } + + if (nbits) res.push_back(base64Chars[data << (6 - nbits) & 0x3f]); + while (res.size() % 4) res.push_back('='); + + return res; +} + + +std::string base64Decode(std::string_view s) +{ + constexpr char npos = -1; + constexpr std::array base64DecodeChars = [&]() { + std::array result{}; + for (auto& c : result) + c = npos; + for (int i = 0; i < 64; i++) + result[base64Chars[i]] = i; + return result; + }(); + + std::string res; + // Some sequences are missing the padding consisting of up to two '='. + // vvv + res.reserve((s.size() + 2) / 4 * 3); + unsigned int d = 0, bits = 0; + + for (char c : s) { + if (c == '=') break; + if (c == '\n') continue; + + char digit = base64DecodeChars[(unsigned char) c]; + if (digit == npos) + throw Error("invalid character in Base64 string: '%c'", c); + + bits += 6; + d = d << 6 | digit; + if (bits >= 8) { + res.push_back(d >> (bits - 8) & 0xff); + bits -= 8; + } + } + + return res; +} + + +std::string stripIndentation(std::string_view s) +{ + size_t minIndent = 10000; + size_t curIndent = 0; + bool atStartOfLine = true; + + for (auto & c : s) { + if (atStartOfLine && c == ' ') + curIndent++; + else if (c == '\n') { + if (atStartOfLine) + minIndent = std::max(minIndent, curIndent); + curIndent = 0; + atStartOfLine = true; + } else { + if (atStartOfLine) { + minIndent = std::min(minIndent, curIndent); + atStartOfLine = false; + } + } + } + + std::string res; + + size_t pos = 0; + while (pos < s.size()) { + auto eol = s.find('\n', pos); + if (eol == s.npos) eol = s.size(); + if (eol - pos > minIndent) + res.append(s.substr(pos + minIndent, eol - pos - minIndent)); + res.push_back('\n'); + pos = eol + 1; + } + + return res; +} + + +std::pair getLine(std::string_view s) +{ + auto newline = s.find('\n'); + + if (newline == s.npos) { + return {s, ""}; + } else { + auto line = s.substr(0, newline); + if (!line.empty() && line[line.size() - 1] == '\r') + line = line.substr(0, line.size() - 1); + return {line, s.substr(newline + 1)}; + } +} + +std::string showBytes(uint64_t bytes) +{ + return fmt("%.2f MiB", bytes / (1024.0 * 1024.0)); +} + +} diff --git a/src/libutil/strings.hh b/src/libutil/strings.hh new file mode 100644 index 000000000..daeb5be50 --- /dev/null +++ b/src/libutil/strings.hh @@ -0,0 +1,256 @@ +#pragma once +///@file + +#include "error.hh" +#include "types.hh" + +#include +#include + +namespace nix { + +/** + * Tree formatting. + */ +constexpr char treeConn[] = "├───"; +constexpr char treeLast[] = "└───"; +constexpr char treeLine[] = "│ "; +constexpr char treeNull[] = " "; + +/** + * Convert a list of strings to a null-terminated vector of `char + * *`s. The result must not be accessed beyond the lifetime of the + * list of strings. + */ +std::vector stringsToCharPtrs(const Strings & ss); + + +MakeError(FormatError, Error); + + +/** + * String tokenizer. + */ +template C tokenizeString(std::string_view s, std::string_view separators = " \t\n\r"); + + +/** + * Concatenate the given strings with a separator between the + * elements. + */ +template +std::string concatStringsSep(const std::string_view sep, const C & ss) +{ + size_t size = 0; + // need a cast to string_view since this is also called with Symbols + for (const auto & s : ss) size += sep.size() + std::string_view(s).size(); + std::string s; + s.reserve(size); + for (auto & i : ss) { + if (s.size() != 0) s += sep; + s += i; + } + return s; +} + +template +auto concatStrings(Parts && ... parts) + -> std::enable_if_t<(... && std::is_convertible_v), std::string> +{ + std::string_view views[sizeof...(parts)] = { parts... }; + return concatStringsSep({}, views); +} + + +/** + * Add quotes around a collection of strings. + */ +template Strings quoteStrings(const C & c) +{ + Strings res; + for (auto & s : c) + res.push_back("'" + s + "'"); + return res; +} + +/** + * Remove trailing whitespace from a string. + * + * \todo return std::string_view. + */ +std::string chomp(std::string_view s); + + +/** + * Remove whitespace from the start and end of a string. + */ +std::string trim(std::string_view s, std::string_view whitespace = " \n\r\t"); + + +/** + * Replace all occurrences of a string inside another string. + */ +std::string replaceStrings( + std::string s, + std::string_view from, + std::string_view to); + + +/** + * Rewrites a string given a map of replacements, applying the replacements in + * sorted order, only once, considering only the strings appearing in the input + * string in performing replacement. + * + * - Replacements are not performed on intermediate strings. That is, for an input + * `"abb"` with replacements `{"ab" -> "ba"}`, the result is `"bab"`. + * - Transitive replacements are not performed. For example, for the input `"abcde"` + * with replacements `{"a" -> "b", "b" -> "c", "e" -> "b"}`, the result is + * `"bccdb"`. + */ +class Rewriter +{ +private: + std::string initials; + std::map rewrites; + +public: + explicit Rewriter(std::map rewrites); + + std::string operator()(std::string s); +}; + +inline std::string rewriteStrings(std::string s, const StringMap & rewrites) +{ + return Rewriter(rewrites)(s); +} + + + +/** + * Parse a string into an integer. + */ +template +std::optional string2Int(const std::string_view s) +{ + if (s.substr(0, 1) == "-" && !std::numeric_limits::is_signed) + return std::nullopt; + try { + return boost::lexical_cast(s.data(), s.size()); + } catch (const boost::bad_lexical_cast &) { + return std::nullopt; + } +} + +/** + * Like string2Int(), but support an optional suffix 'K', 'M', 'G' or + * 'T' denoting a binary unit prefix. + */ +template +N string2IntWithUnitPrefix(std::string_view s) +{ + N multiplier = 1; + if (!s.empty()) { + char u = std::toupper(*s.rbegin()); + if (std::isalpha(u)) { + if (u == 'K') multiplier = 1ULL << 10; + else if (u == 'M') multiplier = 1ULL << 20; + else if (u == 'G') multiplier = 1ULL << 30; + else if (u == 'T') multiplier = 1ULL << 40; + else throw UsageError("invalid unit specifier '%1%'", u); + s.remove_suffix(1); + } + } + if (auto n = string2Int(s)) + return *n * multiplier; + throw UsageError("'%s' is not an integer", s); +} + +/** + * Parse a string into a float. + */ +template +std::optional string2Float(const std::string_view s) +{ + try { + return boost::lexical_cast(s.data(), s.size()); + } catch (const boost::bad_lexical_cast &) { + return std::nullopt; + } +} + + +/** + * Convert a little-endian integer to host order. + */ +template +T readLittleEndian(unsigned char * p) +{ + T x = 0; + for (size_t i = 0; i < sizeof(x); ++i, ++p) { + x |= ((T) *p) << (i * 8); + } + return x; +} + +/** + * Convert a string to lower case. + */ +std::string toLower(const std::string & s); + + +/** + * Escape a string as a shell word. + */ +std::string shellEscape(const std::string_view s); + +/** + * Base64 encoding/decoding. + */ +std::string base64Encode(std::string_view s); +std::string base64Decode(std::string_view s); + + +/** + * Remove common leading whitespace from the lines in the string + * 's'. For example, if every line is indented by at least 3 spaces, + * then we remove 3 spaces from the start of every line. + */ +std::string stripIndentation(std::string_view s); + +/** + * Get the prefix of 's' up to and excluding the next line break (LF + * optionally preceded by CR), and the remainder following the line + * break. + */ +std::pair getLine(std::string_view s); + +std::string showBytes(uint64_t bytes); + + +/** + * Provide an addition operator between strings and string_views + * inexplicably omitted from the standard library. + */ +inline std::string operator + (const std::string & s1, std::string_view s2) +{ + auto s = s1; + s.append(s2); + return s; +} + +inline std::string operator + (std::string && s, std::string_view s2) +{ + s.append(s2); + return std::move(s); +} + +inline std::string operator + (std::string_view s1, const char * s2) +{ + std::string s; + s.reserve(s1.size() + strlen(s2)); + s.append(s1); + s.append(s2); + return s; +} + +} diff --git a/src/libutil/unix-domain-socket.cc b/src/libutil/unix-domain-socket.cc index 9fefcbe1c..a9a2a415a 100644 --- a/src/libutil/unix-domain-socket.cc +++ b/src/libutil/unix-domain-socket.cc @@ -1,7 +1,7 @@ #include "file-system.hh" #include "processes.hh" #include "unix-domain-socket.hh" -#include "util.hh" +#include "strings.hh" #include #include diff --git a/src/libutil/url.cc b/src/libutil/url.cc index afccc4245..46688cef5 100644 --- a/src/libutil/url.cc +++ b/src/libutil/url.cc @@ -1,7 +1,7 @@ #include "url.hh" #include "url-parts.hh" -#include "util.hh" #include "split.hh" +#include "strings.hh" namespace nix { diff --git a/src/libutil/util.cc b/src/libutil/util.cc index f580ef038..099a07622 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1,5 +1,6 @@ #include "util.hh" #include "processes.hh" +#include "strings.hh" #include "current-process.hh" #include "sync.hh" @@ -164,116 +165,6 @@ Path createNixStateDir() -std::vector stringsToCharPtrs(const Strings & ss) -{ - std::vector res; - for (auto & s : ss) res.push_back((char *) s.c_str()); - res.push_back(0); - return res; -} - - -////////////////////////////////////////////////////////////////////// - - -template C tokenizeString(std::string_view s, std::string_view separators) -{ - C result; - auto pos = s.find_first_not_of(separators, 0); - while (pos != std::string_view::npos) { - auto end = s.find_first_of(separators, pos + 1); - if (end == std::string_view::npos) end = s.size(); - result.insert(result.end(), std::string(s, pos, end - pos)); - pos = s.find_first_not_of(separators, end); - } - return result; -} - -template Strings tokenizeString(std::string_view s, std::string_view separators); -template StringSet tokenizeString(std::string_view s, std::string_view separators); -template std::vector tokenizeString(std::string_view s, std::string_view separators); - - -std::string chomp(std::string_view s) -{ - size_t i = s.find_last_not_of(" \n\r\t"); - return i == std::string_view::npos ? "" : std::string(s, 0, i + 1); -} - - -std::string trim(std::string_view s, std::string_view whitespace) -{ - auto i = s.find_first_not_of(whitespace); - if (i == s.npos) return ""; - auto j = s.find_last_not_of(whitespace); - return std::string(s, i, j == s.npos ? j : j - i + 1); -} - - -std::string replaceStrings( - std::string res, - std::string_view from, - std::string_view to) -{ - if (from.empty()) return res; - size_t pos = 0; - while ((pos = res.find(from, pos)) != std::string::npos) { - res.replace(pos, from.size(), to); - pos += to.size(); - } - return res; -} - - -Rewriter::Rewriter(std::map rewrites) - : rewrites(std::move(rewrites)) -{ - for (const auto & [k, v] : this->rewrites) { - assert(!k.empty()); - initials.push_back(k[0]); - } - std::ranges::sort(initials); - auto [firstDupe, end] = std::ranges::unique(initials); - initials.erase(firstDupe, end); -} - -std::string Rewriter::operator()(std::string s) -{ - size_t j = 0; - while ((j = s.find_first_of(initials, j)) != std::string::npos) { - size_t skip = 1; - for (auto & [from, to] : rewrites) { - if (s.compare(j, from.size(), from) == 0) { - s.replace(j, from.size(), to); - skip = to.size(); - break; - } - } - j += skip; - } - return s; -} - - -std::string toLower(const std::string & s) -{ - std::string r(s); - for (auto & c : r) - c = std::tolower(c); - return r; -} - - -std::string shellEscape(const std::string_view s) -{ - std::string r; - r.reserve(s.size() + 2); - r += "'"; - for (auto & i : s) - if (i == '\'') r += "'\\''"; else r += i; - r += '\''; - return r; -} void ignoreException(Verbosity lvl) @@ -289,120 +180,6 @@ void ignoreException(Verbosity lvl) } catch (...) { } } -constexpr char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -std::string base64Encode(std::string_view s) -{ - std::string res; - res.reserve((s.size() + 2) / 3 * 4); - int data = 0, nbits = 0; - - for (char c : s) { - data = data << 8 | (unsigned char) c; - nbits += 8; - while (nbits >= 6) { - nbits -= 6; - res.push_back(base64Chars[data >> nbits & 0x3f]); - } - } - - if (nbits) res.push_back(base64Chars[data << (6 - nbits) & 0x3f]); - while (res.size() % 4) res.push_back('='); - - return res; -} - - -std::string base64Decode(std::string_view s) -{ - constexpr char npos = -1; - constexpr std::array base64DecodeChars = [&]() { - std::array result{}; - for (auto& c : result) - c = npos; - for (int i = 0; i < 64; i++) - result[base64Chars[i]] = i; - return result; - }(); - - std::string res; - // Some sequences are missing the padding consisting of up to two '='. - // vvv - res.reserve((s.size() + 2) / 4 * 3); - unsigned int d = 0, bits = 0; - - for (char c : s) { - if (c == '=') break; - if (c == '\n') continue; - - char digit = base64DecodeChars[(unsigned char) c]; - if (digit == npos) - throw Error("invalid character in Base64 string: '%c'", c); - - bits += 6; - d = d << 6 | digit; - if (bits >= 8) { - res.push_back(d >> (bits - 8) & 0xff); - bits -= 8; - } - } - - return res; -} - - -std::string stripIndentation(std::string_view s) -{ - size_t minIndent = 10000; - size_t curIndent = 0; - bool atStartOfLine = true; - - for (auto & c : s) { - if (atStartOfLine && c == ' ') - curIndent++; - else if (c == '\n') { - if (atStartOfLine) - minIndent = std::max(minIndent, curIndent); - curIndent = 0; - atStartOfLine = true; - } else { - if (atStartOfLine) { - minIndent = std::min(minIndent, curIndent); - atStartOfLine = false; - } - } - } - - std::string res; - - size_t pos = 0; - while (pos < s.size()) { - auto eol = s.find('\n', pos); - if (eol == s.npos) eol = s.size(); - if (eol - pos > minIndent) - res.append(s.substr(pos + minIndent, eol - pos - minIndent)); - res.push_back('\n'); - pos = eol + 1; - } - - return res; -} - - -std::pair getLine(std::string_view s) -{ - auto newline = s.find('\n'); - - if (newline == s.npos) { - return {s, ""}; - } else { - auto line = s.substr(0, newline); - if (!line.empty() && line[line.size() - 1] == '\r') - line = line.substr(0, line.size() - 1); - return {line, s.substr(newline + 1)}; - } -} - ////////////////////////////////////////////////////////////////////// @@ -460,9 +237,4 @@ void unshareFilesystem() } -std::string showBytes(uint64_t bytes) -{ - return fmt("%.2f MiB", bytes / (1024.0 * 1024.0)); -} - } diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 216693635..907bdf4ed 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -106,193 +106,6 @@ void restoreMountNamespace(); void unshareFilesystem(); -/** - * Convert a list of strings to a null-terminated vector of `char - * *`s. The result must not be accessed beyond the lifetime of the - * list of strings. - */ -std::vector stringsToCharPtrs(const Strings & ss); - - -MakeError(FormatError, Error); - - -/** - * String tokenizer. - */ -template C tokenizeString(std::string_view s, std::string_view separators = " \t\n\r"); - - -/** - * Concatenate the given strings with a separator between the - * elements. - */ -template -std::string concatStringsSep(const std::string_view sep, const C & ss) -{ - size_t size = 0; - // need a cast to string_view since this is also called with Symbols - for (const auto & s : ss) size += sep.size() + std::string_view(s).size(); - std::string s; - s.reserve(size); - for (auto & i : ss) { - if (s.size() != 0) s += sep; - s += i; - } - return s; -} - -template -auto concatStrings(Parts && ... parts) - -> std::enable_if_t<(... && std::is_convertible_v), std::string> -{ - std::string_view views[sizeof...(parts)] = { parts... }; - return concatStringsSep({}, views); -} - - -/** - * Add quotes around a collection of strings. - */ -template Strings quoteStrings(const C & c) -{ - Strings res; - for (auto & s : c) - res.push_back("'" + s + "'"); - return res; -} - -/** - * Remove trailing whitespace from a string. - * - * \todo return std::string_view. - */ -std::string chomp(std::string_view s); - - -/** - * Remove whitespace from the start and end of a string. - */ -std::string trim(std::string_view s, std::string_view whitespace = " \n\r\t"); - - -/** - * Replace all occurrences of a string inside another string. - */ -std::string replaceStrings( - std::string s, - std::string_view from, - std::string_view to); - - -/** - * Rewrites a string given a map of replacements, applying the replacements in - * sorted order, only once, considering only the strings appearing in the input - * string in performing replacement. - * - * - Replacements are not performed on intermediate strings. That is, for an input - * `"abb"` with replacements `{"ab" -> "ba"}`, the result is `"bab"`. - * - Transitive replacements are not performed. For example, for the input `"abcde"` - * with replacements `{"a" -> "b", "b" -> "c", "e" -> "b"}`, the result is - * `"bccdb"`. - */ -class Rewriter -{ -private: - std::string initials; - std::map rewrites; - -public: - explicit Rewriter(std::map rewrites); - - std::string operator()(std::string s); -}; - -inline std::string rewriteStrings(std::string s, const StringMap & rewrites) -{ - return Rewriter(rewrites)(s); -} - - - -/** - * Parse a string into an integer. - */ -template -std::optional string2Int(const std::string_view s) -{ - if (s.substr(0, 1) == "-" && !std::numeric_limits::is_signed) - return std::nullopt; - try { - return boost::lexical_cast(s.data(), s.size()); - } catch (const boost::bad_lexical_cast &) { - return std::nullopt; - } -} - -/** - * Like string2Int(), but support an optional suffix 'K', 'M', 'G' or - * 'T' denoting a binary unit prefix. - */ -template -N string2IntWithUnitPrefix(std::string_view s) -{ - N multiplier = 1; - if (!s.empty()) { - char u = std::toupper(*s.rbegin()); - if (std::isalpha(u)) { - if (u == 'K') multiplier = 1ULL << 10; - else if (u == 'M') multiplier = 1ULL << 20; - else if (u == 'G') multiplier = 1ULL << 30; - else if (u == 'T') multiplier = 1ULL << 40; - else throw UsageError("invalid unit specifier '%1%'", u); - s.remove_suffix(1); - } - } - if (auto n = string2Int(s)) - return *n * multiplier; - throw UsageError("'%s' is not an integer", s); -} - -/** - * Parse a string into a float. - */ -template -std::optional string2Float(const std::string_view s) -{ - try { - return boost::lexical_cast(s.data(), s.size()); - } catch (const boost::bad_lexical_cast &) { - return std::nullopt; - } -} - - -/** - * Convert a little-endian integer to host order. - */ -template -T readLittleEndian(unsigned char * p) -{ - T x = 0; - for (size_t i = 0; i < sizeof(x); ++i, ++p) { - x |= ((T) *p) << (i * 8); - } - return x; -} - -/** - * Convert a string to lower case. - */ -std::string toLower(const std::string & s); - - -/** - * Escape a string as a shell word. - */ -std::string shellEscape(const std::string_view s); - - /** * Exception handling in destructors: print an error message, then * ignore the exception. @@ -301,38 +114,6 @@ void ignoreException(Verbosity lvl = lvlError); -/** - * Tree formatting. - */ -constexpr char treeConn[] = "├───"; -constexpr char treeLast[] = "└───"; -constexpr char treeLine[] = "│ "; -constexpr char treeNull[] = " "; - - -/** - * Base64 encoding/decoding. - */ -std::string base64Encode(std::string_view s); -std::string base64Decode(std::string_view s); - - -/** - * Remove common leading whitespace from the lines in the string - * 's'. For example, if every line is indented by at least 3 spaces, - * then we remove 3 spaces from the start of every line. - */ -std::string stripIndentation(std::string_view s); - - -/** - * Get the prefix of 's' up to and excluding the next line break (LF - * optionally preceded by CR), and the remainder following the line - * break. - */ -std::pair getLine(std::string_view s); - - /** * Get a value for the specified key from an associate container. */ @@ -443,33 +224,4 @@ template struct overloaded : Ts... { using Ts::operator()...; }; template overloaded(Ts...) -> overloaded; -std::string showBytes(uint64_t bytes); - - -/** - * Provide an addition operator between strings and string_views - * inexplicably omitted from the standard library. - */ -inline std::string operator + (const std::string & s1, std::string_view s2) -{ - auto s = s1; - s.append(s2); - return s; -} - -inline std::string operator + (std::string && s, std::string_view s2) -{ - s.append(s2); - return std::move(s); -} - -inline std::string operator + (std::string_view s1, const char * s2) -{ - std::string s; - s.reserve(s1.size() + strlen(s2)); - s.append(s1); - s.append(s2); - return s; -} - } diff --git a/tests/functional/repl_characterization/repl_characterization.cc b/tests/functional/repl_characterization/repl_characterization.cc index 0d3e5352d..4cc1e7e37 100644 --- a/tests/functional/repl_characterization/repl_characterization.cc +++ b/tests/functional/repl_characterization/repl_characterization.cc @@ -12,6 +12,7 @@ #include "tests/cli-literate-parser.hh" #include "tests/terminal-code-eater.hh" #include "util.hh" +#include "strings.hh" using namespace std::string_literals; diff --git a/tests/functional/repl_characterization/test-session.cc b/tests/functional/repl_characterization/test-session.cc index ab0710b24..be3d407f3 100644 --- a/tests/functional/repl_characterization/test-session.cc +++ b/tests/functional/repl_characterization/test-session.cc @@ -3,9 +3,9 @@ #include #include "test-session.hh" -#include "util.hh" #include "escape-char.hh" #include "processes.hh" +#include "strings.hh" namespace nix { diff --git a/tests/unit/libstore/machines.cc b/tests/unit/libstore/machines.cc index dc192e5be..ba27d85b7 100644 --- a/tests/unit/libstore/machines.cc +++ b/tests/unit/libstore/machines.cc @@ -1,6 +1,7 @@ #include "file-system.hh" #include "machines.hh" #include "globals.hh" +#include "strings.hh" #include "tests/test-data.hh" #include diff --git a/tests/unit/libutil-support/tests/cli-literate-parser.cc b/tests/unit/libutil-support/tests/cli-literate-parser.cc index 822f5e276..69bf9fb46 100644 --- a/tests/unit/libutil-support/tests/cli-literate-parser.cc +++ b/tests/unit/libutil-support/tests/cli-literate-parser.cc @@ -19,6 +19,7 @@ #include "shlex.hh" #include "types.hh" #include "util.hh" +#include "strings.hh" static constexpr const bool DEBUG_PARSER = false; diff --git a/tests/unit/libutil/tests.cc b/tests/unit/libutil/tests.cc index a42afd997..324ebd335 100644 --- a/tests/unit/libutil/tests.cc +++ b/tests/unit/libutil/tests.cc @@ -1,8 +1,9 @@ #include "file-system.hh" -#include "util.hh" #include "processes.hh" +#include "strings.hh" #include "types.hh" #include "terminal.hh" +#include "util.hh" #include