mirror of
https://github.com/NixOS/nix.git
synced 2025-11-11 21:16:02 +01:00
Merge branch 'master' into debug-step
This commit is contained in:
commit
dbe3fd3735
178 changed files with 2886 additions and 28663 deletions
|
|
@ -93,13 +93,12 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter)
|
|||
debug(format("removing case hack suffix from '%1%'") % (path + "/" + i.name));
|
||||
name.erase(pos);
|
||||
}
|
||||
if (unhacked.find(name) != unhacked.end())
|
||||
if (!unhacked.emplace(name, i.name).second)
|
||||
throw Error("file name collision in between '%1%' and '%2%'",
|
||||
(path + "/" + unhacked[name]),
|
||||
(path + "/" + i.name));
|
||||
unhacked[name] = i.name;
|
||||
} else
|
||||
unhacked[i.name] = i.name;
|
||||
unhacked.emplace(i.name, i.name);
|
||||
|
||||
for (auto & i : unhacked)
|
||||
if (filter(path + "/" + i.first)) {
|
||||
|
|
|
|||
|
|
@ -190,13 +190,13 @@ struct BrotliDecompressionSink : ChunkedCompressionSink
|
|||
}
|
||||
};
|
||||
|
||||
ref<std::string> decompress(const std::string & method, const std::string & in)
|
||||
std::string decompress(const std::string & method, std::string_view in)
|
||||
{
|
||||
StringSink ssink;
|
||||
auto sink = makeDecompressionSink(method, ssink);
|
||||
(*sink)(in);
|
||||
sink->finish();
|
||||
return ssink.s;
|
||||
return std::move(ssink.s);
|
||||
}
|
||||
|
||||
std::unique_ptr<FinishSink> makeDecompressionSink(const std::string & method, Sink & nextSink)
|
||||
|
|
@ -281,13 +281,13 @@ ref<CompressionSink> makeCompressionSink(const std::string & method, Sink & next
|
|||
throw UnknownCompressionMethod("unknown compression method '%s'", method);
|
||||
}
|
||||
|
||||
ref<std::string> compress(const std::string & method, const std::string & in, const bool parallel, int level)
|
||||
std::string compress(const std::string & method, std::string_view in, const bool parallel, int level)
|
||||
{
|
||||
StringSink ssink;
|
||||
auto sink = makeCompressionSink(method, ssink, parallel, level);
|
||||
(*sink)(in);
|
||||
sink->finish();
|
||||
return ssink.s;
|
||||
return std::move(ssink.s);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ struct CompressionSink : BufferedSink, FinishSink
|
|||
using FinishSink::finish;
|
||||
};
|
||||
|
||||
ref<std::string> decompress(const std::string & method, const std::string & in);
|
||||
std::string decompress(const std::string & method, std::string_view in);
|
||||
|
||||
std::unique_ptr<FinishSink> makeDecompressionSink(const std::string & method, Sink & nextSink);
|
||||
|
||||
ref<std::string> compress(const std::string & method, const std::string & in, const bool parallel = false, int level = -1);
|
||||
std::string compress(const std::string & method, std::string_view in, const bool parallel = false, int level = -1);
|
||||
|
||||
ref<CompressionSink> makeCompressionSink(const std::string & method, Sink & nextSink, const bool parallel = false, int level = -1);
|
||||
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ public:
|
|||
{ }
|
||||
|
||||
template<typename... Args>
|
||||
BaseError(const std::string & fs, const Args & ... args)
|
||||
explicit BaseError(const std::string & fs, const Args & ... args)
|
||||
: err { .level = lvlError, .msg = hintfmt(fs, args...) }
|
||||
{ }
|
||||
|
||||
|
|
|
|||
46
src/libutil/fmt.cc
Normal file
46
src/libutil/fmt.cc
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#include "fmt.hh"
|
||||
|
||||
#include <regex>
|
||||
|
||||
namespace nix {
|
||||
|
||||
std::string hiliteMatches(
|
||||
std::string_view s,
|
||||
std::vector<std::smatch> matches,
|
||||
std::string_view prefix,
|
||||
std::string_view postfix)
|
||||
{
|
||||
// Avoid copy on zero matches
|
||||
if (matches.size() == 0)
|
||||
return (std::string) s;
|
||||
|
||||
std::sort(matches.begin(), matches.end(), [](const auto & a, const auto & b) {
|
||||
return a.position() < b.position();
|
||||
});
|
||||
|
||||
std::string out;
|
||||
ssize_t last_end = 0;
|
||||
|
||||
for (auto it = matches.begin(); it != matches.end();) {
|
||||
auto m = *it;
|
||||
size_t start = m.position();
|
||||
out.append(s.substr(last_end, m.position() - last_end));
|
||||
// Merge continous matches
|
||||
ssize_t end = start + m.length();
|
||||
while (++it != matches.end() && (*it).position() <= end) {
|
||||
auto n = *it;
|
||||
ssize_t nend = start + (n.position() - start + n.length());
|
||||
if (nend > end)
|
||||
end = nend;
|
||||
}
|
||||
out.append(prefix);
|
||||
out.append(s.substr(start, end - start));
|
||||
out.append(postfix);
|
||||
last_end = end;
|
||||
}
|
||||
|
||||
out.append(s.substr(last_end));
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <boost/format.hpp>
|
||||
#include <string>
|
||||
#include <regex>
|
||||
#include "ansicolor.hh"
|
||||
|
||||
|
||||
|
|
@ -154,4 +155,16 @@ inline hintformat hintfmt(std::string plain_string)
|
|||
// we won't be receiving any args in this case, so just print the original string
|
||||
return hintfmt("%s", normaltxt(plain_string));
|
||||
}
|
||||
|
||||
/* Highlight all the given matches in the given string `s` by wrapping
|
||||
them between `prefix` and `postfix`.
|
||||
|
||||
If some matches overlap, then their union will be wrapped rather
|
||||
than the individual matches. */
|
||||
std::string hiliteMatches(
|
||||
std::string_view s,
|
||||
std::vector<std::smatch> matches,
|
||||
std::string_view prefix,
|
||||
std::string_view postfix);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@ Hash::Hash(std::string_view rest, HashType type, bool isSRI)
|
|||
throw BadHash("hash '%s' has wrong length for hash type '%s'", rest, printHashType(this->type));
|
||||
}
|
||||
|
||||
Hash newHashAllowEmpty(std::string hashStr, std::optional<HashType> ht)
|
||||
Hash newHashAllowEmpty(std::string_view hashStr, std::optional<HashType> ht)
|
||||
{
|
||||
if (hashStr.empty()) {
|
||||
if (!ht)
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ public:
|
|||
};
|
||||
|
||||
/* Helper that defaults empty hashes to the 0 hash. */
|
||||
Hash newHashAllowEmpty(std::string hashStr, std::optional<HashType> ht);
|
||||
Hash newHashAllowEmpty(std::string_view hashStr, std::optional<HashType> ht);
|
||||
|
||||
/* Print a hash in base-16 if it's MD5, or base-32 otherwise. */
|
||||
string printHash16or32(const Hash & hash);
|
||||
|
|
|
|||
|
|
@ -7,16 +7,38 @@ namespace nix {
|
|||
|
||||
void toJSON(std::ostream & str, const char * start, const char * end)
|
||||
{
|
||||
str << '"';
|
||||
for (auto i = start; i != end; i++)
|
||||
if (*i == '\"' || *i == '\\') str << '\\' << *i;
|
||||
else if (*i == '\n') str << "\\n";
|
||||
else if (*i == '\r') str << "\\r";
|
||||
else if (*i == '\t') str << "\\t";
|
||||
else if (*i >= 0 && *i < 32)
|
||||
str << "\\u" << std::setfill('0') << std::setw(4) << std::hex << (uint16_t) *i << std::dec;
|
||||
else str << *i;
|
||||
str << '"';
|
||||
constexpr size_t BUF_SIZE = 4096;
|
||||
char buf[BUF_SIZE + 7]; // BUF_SIZE + largest single sequence of puts
|
||||
size_t bufPos = 0;
|
||||
|
||||
const auto flush = [&] {
|
||||
str.write(buf, bufPos);
|
||||
bufPos = 0;
|
||||
};
|
||||
const auto put = [&] (char c) {
|
||||
buf[bufPos++] = c;
|
||||
};
|
||||
|
||||
put('"');
|
||||
for (auto i = start; i != end; i++) {
|
||||
if (bufPos >= BUF_SIZE) flush();
|
||||
if (*i == '\"' || *i == '\\') { put('\\'); put(*i); }
|
||||
else if (*i == '\n') { put('\\'); put('n'); }
|
||||
else if (*i == '\r') { put('\\'); put('r'); }
|
||||
else if (*i == '\t') { put('\\'); put('t'); }
|
||||
else if (*i >= 0 && *i < 32) {
|
||||
const char hex[17] = "0123456789abcdef";
|
||||
put('\\');
|
||||
put('u');
|
||||
put(hex[(uint16_t(*i) >> 12) & 0xf]);
|
||||
put(hex[(uint16_t(*i) >> 8) & 0xf]);
|
||||
put(hex[(uint16_t(*i) >> 4) & 0xf]);
|
||||
put(hex[(uint16_t(*i) >> 0) & 0xf]);
|
||||
}
|
||||
else put(*i);
|
||||
}
|
||||
put('"');
|
||||
flush();
|
||||
}
|
||||
|
||||
void toJSON(std::ostream & str, const char * s)
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ struct LoggerSettings : Config
|
|||
Setting<bool> showTrace{
|
||||
this, false, "show-trace",
|
||||
R"(
|
||||
Where Nix should print out a stack trace in case of Nix
|
||||
Whether Nix should print out a stack trace in case of Nix
|
||||
expression evaluation errors.
|
||||
)"};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
#if 0
|
||||
#include "logging.hh"
|
||||
#include "rust-ffi.hh"
|
||||
|
||||
extern "C" std::exception_ptr * make_error(rust::StringSlice s)
|
||||
{
|
||||
return new std::exception_ptr(std::make_exception_ptr(nix::Error(std::string(s.ptr, s.size))));
|
||||
}
|
||||
|
||||
extern "C" void destroy_error(std::exception_ptr * ex)
|
||||
{
|
||||
free(ex);
|
||||
}
|
||||
|
||||
namespace rust {
|
||||
|
||||
std::ostream & operator << (std::ostream & str, const String & s)
|
||||
{
|
||||
str << (std::string_view) s;
|
||||
return str;
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
#pragma once
|
||||
#if 0
|
||||
|
||||
#include "serialise.hh"
|
||||
|
||||
#include <string_view>
|
||||
#include <cstring>
|
||||
#include <array>
|
||||
|
||||
namespace rust {
|
||||
|
||||
typedef void (*DropFun)(void *);
|
||||
|
||||
/* A Rust value of N bytes. It can be moved but not copied. When it
|
||||
goes out of scope, the C++ destructor will run the drop
|
||||
function. */
|
||||
template<std::size_t N, DropFun drop>
|
||||
struct Value
|
||||
{
|
||||
protected:
|
||||
|
||||
std::array<char, N> raw;
|
||||
|
||||
~Value()
|
||||
{
|
||||
if (!isEvacuated()) {
|
||||
drop(this);
|
||||
evacuate();
|
||||
}
|
||||
}
|
||||
|
||||
// Must not be called directly.
|
||||
Value()
|
||||
{ }
|
||||
|
||||
Value(Value && other)
|
||||
: raw(other.raw)
|
||||
{
|
||||
other.evacuate();
|
||||
}
|
||||
|
||||
void operator =(Value && other)
|
||||
{
|
||||
if (!isEvacuated())
|
||||
drop(this);
|
||||
raw = other.raw;
|
||||
other.evacuate();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/* FIXME: optimize these (ideally in such a way that the compiler
|
||||
can elide most calls to evacuate() / isEvacuated(). */
|
||||
inline void evacuate()
|
||||
{
|
||||
for (auto & i : raw) i = 0;
|
||||
}
|
||||
|
||||
inline bool isEvacuated()
|
||||
{
|
||||
for (auto & i : raw)
|
||||
if (i != 0) return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/* A Rust vector. */
|
||||
template<typename T, DropFun drop>
|
||||
struct Vec : Value<3 * sizeof(void *), drop>
|
||||
{
|
||||
inline size_t size() const
|
||||
{
|
||||
return ((const size_t *) &this->raw)[2];
|
||||
}
|
||||
|
||||
const T * data() const
|
||||
{
|
||||
return ((const T * *) &this->raw)[0];
|
||||
}
|
||||
};
|
||||
|
||||
/* A Rust slice. */
|
||||
template<typename T>
|
||||
struct Slice
|
||||
{
|
||||
const T * ptr;
|
||||
size_t size;
|
||||
|
||||
Slice(const T * ptr, size_t size) : ptr(ptr), size(size)
|
||||
{
|
||||
assert(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
struct StringSlice : Slice<char>
|
||||
{
|
||||
StringSlice(const std::string & s): Slice(s.data(), s.size()) {}
|
||||
explicit StringSlice(std::string_view s): Slice(s.data(), s.size()) {}
|
||||
StringSlice(const char * s): Slice(s, strlen(s)) {}
|
||||
|
||||
operator std::string_view() const
|
||||
{
|
||||
return std::string_view(ptr, size);
|
||||
}
|
||||
};
|
||||
|
||||
/* A Rust string. */
|
||||
struct String;
|
||||
|
||||
extern "C" {
|
||||
void ffi_String_new(StringSlice s, String * out);
|
||||
void ffi_String_drop(void * s);
|
||||
}
|
||||
|
||||
struct String : Vec<char, ffi_String_drop>
|
||||
{
|
||||
String() = delete;
|
||||
|
||||
String(std::string_view s)
|
||||
{
|
||||
ffi_String_new(StringSlice(s), this);
|
||||
}
|
||||
|
||||
String(const char * s)
|
||||
: String({s, std::strlen(s)})
|
||||
{
|
||||
}
|
||||
|
||||
operator std::string_view() const
|
||||
{
|
||||
return std::string_view(data(), size());
|
||||
}
|
||||
};
|
||||
|
||||
std::ostream & operator << (std::ostream & str, const String & s);
|
||||
|
||||
/* C++ representation of Rust's Result<T, CppException>. */
|
||||
template<typename T>
|
||||
struct Result
|
||||
{
|
||||
enum { Ok = 0, Err = 1, Uninit = 2 } tag;
|
||||
|
||||
union {
|
||||
T data;
|
||||
std::exception_ptr * exc;
|
||||
};
|
||||
|
||||
Result() : tag(Uninit) { }; // FIXME: remove
|
||||
|
||||
Result(const Result &) = delete;
|
||||
|
||||
Result(Result && other)
|
||||
: tag(other.tag)
|
||||
{
|
||||
other.tag = Uninit;
|
||||
if (tag == Ok)
|
||||
data = std::move(other.data);
|
||||
else if (tag == Err)
|
||||
exc = other.exc;
|
||||
}
|
||||
|
||||
~Result()
|
||||
{
|
||||
if (tag == Ok)
|
||||
data.~T();
|
||||
else if (tag == Err)
|
||||
free(exc);
|
||||
else if (tag == Uninit)
|
||||
;
|
||||
else
|
||||
abort();
|
||||
}
|
||||
|
||||
/* Rethrow the wrapped exception or return the wrapped value. */
|
||||
T unwrap()
|
||||
{
|
||||
if (tag == Ok) {
|
||||
tag = Uninit;
|
||||
return std::move(data);
|
||||
}
|
||||
else if (tag == Err)
|
||||
std::rethrow_exception(*exc);
|
||||
else
|
||||
abort();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
||||
|
|
@ -110,7 +110,7 @@ std::string Source::drain()
|
|||
{
|
||||
StringSink s;
|
||||
drainInto(s);
|
||||
return *s.s;
|
||||
return std::move(s.s);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -325,7 +325,7 @@ void writeString(std::string_view data, Sink & sink)
|
|||
}
|
||||
|
||||
|
||||
Sink & operator << (Sink & sink, const string & s)
|
||||
Sink & operator << (Sink & sink, std::string_view s)
|
||||
{
|
||||
writeString(s, sink);
|
||||
return sink;
|
||||
|
|
@ -450,11 +450,11 @@ Error readError(Source & source)
|
|||
void StringSink::operator () (std::string_view data)
|
||||
{
|
||||
static bool warned = false;
|
||||
if (!warned && s->size() > threshold) {
|
||||
if (!warned && s.size() > threshold) {
|
||||
warnLargeDump();
|
||||
warned = true;
|
||||
}
|
||||
s->append(data);
|
||||
s.append(data);
|
||||
}
|
||||
|
||||
size_t ChainSource::read(char * data, size_t len)
|
||||
|
|
|
|||
|
|
@ -154,12 +154,13 @@ private:
|
|||
/* A sink that writes data to a string. */
|
||||
struct StringSink : Sink
|
||||
{
|
||||
ref<std::string> s;
|
||||
StringSink() : s(make_ref<std::string>()) { };
|
||||
explicit StringSink(const size_t reservedSize) : s(make_ref<std::string>()) {
|
||||
s->reserve(reservedSize);
|
||||
std::string s;
|
||||
StringSink() { }
|
||||
explicit StringSink(const size_t reservedSize)
|
||||
{
|
||||
s.reserve(reservedSize);
|
||||
};
|
||||
StringSink(ref<std::string> s) : s(s) { };
|
||||
StringSink(std::string && s) : s(std::move(s)) { };
|
||||
void operator () (std::string_view data) override;
|
||||
};
|
||||
|
||||
|
|
@ -167,9 +168,9 @@ struct StringSink : Sink
|
|||
/* A source that reads data from a string. */
|
||||
struct StringSource : Source
|
||||
{
|
||||
const string & s;
|
||||
std::string_view s;
|
||||
size_t pos;
|
||||
StringSource(const string & _s) : s(_s), pos(0) { }
|
||||
StringSource(std::string_view s) : s(s), pos(0) { }
|
||||
size_t read(char * data, size_t len) override;
|
||||
};
|
||||
|
||||
|
|
@ -317,10 +318,10 @@ inline Sink & operator << (Sink & sink, uint64_t n)
|
|||
return sink;
|
||||
}
|
||||
|
||||
Sink & operator << (Sink & sink, const string & s);
|
||||
Sink & operator << (Sink & in, const Error & ex);
|
||||
Sink & operator << (Sink & sink, std::string_view s);
|
||||
Sink & operator << (Sink & sink, const Strings & s);
|
||||
Sink & operator << (Sink & sink, const StringSet & s);
|
||||
Sink & operator << (Sink & in, const Error & ex);
|
||||
|
||||
|
||||
MakeError(SerialisationError, Error);
|
||||
|
|
|
|||
|
|
@ -12,17 +12,17 @@ namespace nix {
|
|||
}
|
||||
|
||||
TEST(compress, noneMethodDoesNothingToTheInput) {
|
||||
ref<std::string> o = compress("none", "this-is-a-test");
|
||||
auto o = compress("none", "this-is-a-test");
|
||||
|
||||
ASSERT_EQ(*o, "this-is-a-test");
|
||||
ASSERT_EQ(o, "this-is-a-test");
|
||||
}
|
||||
|
||||
TEST(decompress, decompressNoneCompressed) {
|
||||
auto method = "none";
|
||||
auto str = "slfja;sljfklsa;jfklsjfkl;sdjfkl;sadjfkl;sdjf;lsdfjsadlf";
|
||||
ref<std::string> o = decompress(method, str);
|
||||
auto o = decompress(method, str);
|
||||
|
||||
ASSERT_EQ(*o, str);
|
||||
ASSERT_EQ(o, str);
|
||||
}
|
||||
|
||||
TEST(decompress, decompressEmptyCompressed) {
|
||||
|
|
@ -30,33 +30,33 @@ namespace nix {
|
|||
// (Content-Encoding == "").
|
||||
auto method = "";
|
||||
auto str = "slfja;sljfklsa;jfklsjfkl;sdjfkl;sadjfkl;sdjf;lsdfjsadlf";
|
||||
ref<std::string> o = decompress(method, str);
|
||||
auto o = decompress(method, str);
|
||||
|
||||
ASSERT_EQ(*o, str);
|
||||
ASSERT_EQ(o, str);
|
||||
}
|
||||
|
||||
TEST(decompress, decompressXzCompressed) {
|
||||
auto method = "xz";
|
||||
auto str = "slfja;sljfklsa;jfklsjfkl;sdjfkl;sadjfkl;sdjf;lsdfjsadlf";
|
||||
ref<std::string> o = decompress(method, *compress(method, str));
|
||||
auto o = decompress(method, compress(method, str));
|
||||
|
||||
ASSERT_EQ(*o, str);
|
||||
ASSERT_EQ(o, str);
|
||||
}
|
||||
|
||||
TEST(decompress, decompressBzip2Compressed) {
|
||||
auto method = "bzip2";
|
||||
auto str = "slfja;sljfklsa;jfklsjfkl;sdjfkl;sadjfkl;sdjf;lsdfjsadlf";
|
||||
ref<std::string> o = decompress(method, *compress(method, str));
|
||||
auto o = decompress(method, compress(method, str));
|
||||
|
||||
ASSERT_EQ(*o, str);
|
||||
ASSERT_EQ(o, str);
|
||||
}
|
||||
|
||||
TEST(decompress, decompressBrCompressed) {
|
||||
auto method = "br";
|
||||
auto str = "slfja;sljfklsa;jfklsjfkl;sdjfkl;sadjfkl;sdjf;lsdfjsadlf";
|
||||
ref<std::string> o = decompress(method, *compress(method, str));
|
||||
auto o = decompress(method, compress(method, str));
|
||||
|
||||
ASSERT_EQ(*o, str);
|
||||
ASSERT_EQ(o, str);
|
||||
}
|
||||
|
||||
TEST(decompress, decompressInvalidInputThrowsCompressionError) {
|
||||
|
|
@ -77,7 +77,7 @@ namespace nix {
|
|||
(*sink)(inputString);
|
||||
sink->finish();
|
||||
|
||||
ASSERT_STREQ((*strSink.s).c_str(), inputString);
|
||||
ASSERT_STREQ(strSink.s.c_str(), inputString);
|
||||
}
|
||||
|
||||
TEST(makeCompressionSink, compressAndDecompress) {
|
||||
|
|
@ -90,7 +90,7 @@ namespace nix {
|
|||
sink->finish();
|
||||
decompressionSink->finish();
|
||||
|
||||
ASSERT_STREQ((*strSink.s).c_str(), inputString);
|
||||
ASSERT_STREQ(strSink.s.c_str(), inputString);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
68
src/libutil/tests/fmt.cc
Normal file
68
src/libutil/tests/fmt.cc
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#include "fmt.hh"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <regex>
|
||||
|
||||
namespace nix {
|
||||
/* ----------- tests for fmt.hh -------------------------------------------------*/
|
||||
|
||||
TEST(hiliteMatches, noHighlight) {
|
||||
ASSERT_STREQ(hiliteMatches("Hello, world!", std::vector<std::smatch>(), "(", ")").c_str(), "Hello, world!");
|
||||
}
|
||||
|
||||
TEST(hiliteMatches, simpleHighlight) {
|
||||
std::string str = "Hello, world!";
|
||||
std::regex re = std::regex("world");
|
||||
auto matches = std::vector(std::sregex_iterator(str.begin(), str.end(), re), std::sregex_iterator());
|
||||
ASSERT_STREQ(
|
||||
hiliteMatches(str, matches, "(", ")").c_str(),
|
||||
"Hello, (world)!"
|
||||
);
|
||||
}
|
||||
|
||||
TEST(hiliteMatches, multipleMatches) {
|
||||
std::string str = "Hello, world, world, world, world, world, world, Hello!";
|
||||
std::regex re = std::regex("world");
|
||||
auto matches = std::vector(std::sregex_iterator(str.begin(), str.end(), re), std::sregex_iterator());
|
||||
ASSERT_STREQ(
|
||||
hiliteMatches(str, matches, "(", ")").c_str(),
|
||||
"Hello, (world), (world), (world), (world), (world), (world), Hello!"
|
||||
);
|
||||
}
|
||||
|
||||
TEST(hiliteMatches, overlappingMatches) {
|
||||
std::string str = "world, Hello, world, Hello, world, Hello, world, Hello, world!";
|
||||
std::regex re = std::regex("Hello, world");
|
||||
std::regex re2 = std::regex("world, Hello");
|
||||
auto v = std::vector(std::sregex_iterator(str.begin(), str.end(), re), std::sregex_iterator());
|
||||
for(auto it = std::sregex_iterator(str.begin(), str.end(), re2); it != std::sregex_iterator(); ++it) {
|
||||
v.push_back(*it);
|
||||
}
|
||||
ASSERT_STREQ(
|
||||
hiliteMatches(str, v, "(", ")").c_str(),
|
||||
"(world, Hello, world, Hello, world, Hello, world, Hello, world)!"
|
||||
);
|
||||
}
|
||||
|
||||
TEST(hiliteMatches, complexOverlappingMatches) {
|
||||
std::string str = "legacyPackages.x86_64-linux.git-crypt";
|
||||
std::vector regexes = {
|
||||
std::regex("t-cry"),
|
||||
std::regex("ux\\.git-cry"),
|
||||
std::regex("git-c"),
|
||||
std::regex("pt"),
|
||||
};
|
||||
std::vector<std::smatch> matches;
|
||||
for(auto regex : regexes)
|
||||
{
|
||||
for(auto it = std::sregex_iterator(str.begin(), str.end(), regex); it != std::sregex_iterator(); ++it) {
|
||||
matches.push_back(*it);
|
||||
}
|
||||
}
|
||||
ASSERT_STREQ(
|
||||
hiliteMatches(str, matches, "(", ")").c_str(),
|
||||
"legacyPackages.x86_64-lin(ux.git-crypt)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
#include <set>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace nix {
|
||||
|
|
@ -22,6 +23,7 @@ typedef std::map<string, string> StringMap;
|
|||
/* Paths are just strings. */
|
||||
|
||||
typedef string Path;
|
||||
typedef std::string_view PathView;
|
||||
typedef list<Path> Paths;
|
||||
typedef set<Path> PathSet;
|
||||
|
||||
|
|
@ -46,4 +48,63 @@ struct Explicit {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
/* This wants to be a little bit like rust's Cow type.
|
||||
Some parts of the evaluator benefit greatly from being able to reuse
|
||||
existing allocations for strings, but have to be able to also use
|
||||
newly allocated storage for values.
|
||||
|
||||
We do not define implicit conversions, even with ref qualifiers,
|
||||
since those can easily become ambiguous to the reader and can degrade
|
||||
into copying behaviour we want to avoid. */
|
||||
class BackedStringView {
|
||||
private:
|
||||
std::variant<std::string, std::string_view> data;
|
||||
|
||||
/* Needed to introduce a temporary since operator-> must return
|
||||
a pointer. Without this we'd need to store the view object
|
||||
even when we already own a string. */
|
||||
class Ptr {
|
||||
private:
|
||||
std::string_view view;
|
||||
public:
|
||||
Ptr(std::string_view view): view(view) {}
|
||||
const std::string_view * operator->() const { return &view; }
|
||||
};
|
||||
|
||||
public:
|
||||
BackedStringView(std::string && s): data(std::move(s)) {}
|
||||
BackedStringView(std::string_view sv): data(sv) {}
|
||||
template<size_t N>
|
||||
BackedStringView(const char (& lit)[N]): data(std::string_view(lit)) {}
|
||||
|
||||
BackedStringView(const BackedStringView &) = delete;
|
||||
BackedStringView & operator=(const BackedStringView &) = delete;
|
||||
|
||||
/* We only want move operations defined since the sole purpose of
|
||||
this type is to avoid copies. */
|
||||
BackedStringView(BackedStringView && other) = default;
|
||||
BackedStringView & operator=(BackedStringView && other) = default;
|
||||
|
||||
bool isOwned() const
|
||||
{
|
||||
return std::holds_alternative<std::string>(data);
|
||||
}
|
||||
|
||||
std::string toOwned() &&
|
||||
{
|
||||
return isOwned()
|
||||
? std::move(std::get<std::string>(data))
|
||||
: std::string(std::get<std::string_view>(data));
|
||||
}
|
||||
|
||||
std::string_view operator*() const
|
||||
{
|
||||
return isOwned()
|
||||
? std::get<std::string>(data)
|
||||
: std::get<std::string_view>(data);
|
||||
}
|
||||
Ptr operator->() const { return Ptr(**this); }
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ void replaceEnv(std::map<std::string, std::string> newEnv)
|
|||
}
|
||||
|
||||
|
||||
Path absPath(Path path, std::optional<Path> dir, bool resolveSymlinks)
|
||||
Path absPath(Path path, std::optional<PathView> dir, bool resolveSymlinks)
|
||||
{
|
||||
if (path[0] != '/') {
|
||||
if (!dir) {
|
||||
|
|
@ -95,27 +95,27 @@ Path absPath(Path path, std::optional<Path> dir, bool resolveSymlinks)
|
|||
if (!getcwd(buf, sizeof(buf)))
|
||||
#endif
|
||||
throw SysError("cannot get cwd");
|
||||
dir = buf;
|
||||
path = concatStrings(buf, "/", path);
|
||||
#ifdef __GNU__
|
||||
free(buf);
|
||||
#endif
|
||||
}
|
||||
path = *dir + "/" + path;
|
||||
} else
|
||||
path = concatStrings(*dir, "/", path);
|
||||
}
|
||||
return canonPath(path, resolveSymlinks);
|
||||
}
|
||||
|
||||
|
||||
Path canonPath(const Path & path, bool resolveSymlinks)
|
||||
Path canonPath(PathView path, bool resolveSymlinks)
|
||||
{
|
||||
assert(path != "");
|
||||
|
||||
string s;
|
||||
s.reserve(256);
|
||||
|
||||
if (path[0] != '/')
|
||||
throw Error("not an absolute path: '%1%'", path);
|
||||
|
||||
string::const_iterator i = path.begin(), end = path.end();
|
||||
string temp;
|
||||
|
||||
/* Count the number of times we follow a symlink and stop at some
|
||||
|
|
@ -125,33 +125,37 @@ Path canonPath(const Path & path, bool resolveSymlinks)
|
|||
while (1) {
|
||||
|
||||
/* Skip slashes. */
|
||||
while (i != end && *i == '/') i++;
|
||||
if (i == end) break;
|
||||
while (!path.empty() && path[0] == '/') path.remove_prefix(1);
|
||||
if (path.empty()) break;
|
||||
|
||||
/* Ignore `.'. */
|
||||
if (*i == '.' && (i + 1 == end || i[1] == '/'))
|
||||
i++;
|
||||
if (path == "." || path.substr(0, 2) == "./")
|
||||
path.remove_prefix(1);
|
||||
|
||||
/* If `..', delete the last component. */
|
||||
else if (*i == '.' && i + 1 < end && i[1] == '.' &&
|
||||
(i + 2 == end || i[2] == '/'))
|
||||
else if (path == ".." || path.substr(0, 3) == "../")
|
||||
{
|
||||
if (!s.empty()) s.erase(s.rfind('/'));
|
||||
i += 2;
|
||||
path.remove_prefix(2);
|
||||
}
|
||||
|
||||
/* Normal component; copy it. */
|
||||
else {
|
||||
s += '/';
|
||||
while (i != end && *i != '/') s += *i++;
|
||||
if (const auto slash = path.find('/'); slash == string::npos) {
|
||||
s += path;
|
||||
path = {};
|
||||
} else {
|
||||
s += path.substr(0, slash);
|
||||
path = path.substr(slash);
|
||||
}
|
||||
|
||||
/* If s points to a symlink, resolve it and continue from there */
|
||||
if (resolveSymlinks && isLink(s)) {
|
||||
if (++followCount >= maxFollow)
|
||||
throw Error("infinite symlink recursion in path '%1%'", path);
|
||||
temp = readLink(s) + string(i, end);
|
||||
i = temp.begin();
|
||||
end = temp.end();
|
||||
temp = concatStrings(readLink(s), path);
|
||||
path = temp;
|
||||
if (!temp.empty() && temp[0] == '/') {
|
||||
s.clear(); /* restart for symlinks pointing to absolute path */
|
||||
} else {
|
||||
|
|
@ -164,11 +168,11 @@ Path canonPath(const Path & path, bool resolveSymlinks)
|
|||
}
|
||||
}
|
||||
|
||||
return s.empty() ? "/" : s;
|
||||
return s.empty() ? "/" : std::move(s);
|
||||
}
|
||||
|
||||
|
||||
Path dirOf(const Path & path)
|
||||
Path dirOf(const PathView path)
|
||||
{
|
||||
Path::size_type pos = path.rfind('/');
|
||||
if (pos == string::npos)
|
||||
|
|
@ -196,16 +200,16 @@ std::string_view baseNameOf(std::string_view path)
|
|||
}
|
||||
|
||||
|
||||
bool isInDir(const Path & path, const Path & dir)
|
||||
bool isInDir(std::string_view path, std::string_view dir)
|
||||
{
|
||||
return path[0] == '/'
|
||||
&& string(path, 0, dir.size()) == dir
|
||||
return path.substr(0, 1) == "/"
|
||||
&& path.substr(0, dir.size()) == dir
|
||||
&& path.size() >= dir.size() + 2
|
||||
&& path[dir.size()] == '/';
|
||||
}
|
||||
|
||||
|
||||
bool isDirOrInDir(const Path & path, const Path & dir)
|
||||
bool isDirOrInDir(std::string_view path, std::string_view dir)
|
||||
{
|
||||
return path == dir || isInDir(path, dir);
|
||||
}
|
||||
|
|
@ -668,9 +672,11 @@ void writeFull(int fd, std::string_view s, bool allowInterrupts)
|
|||
|
||||
string drainFD(int fd, bool block, const size_t reserveSize)
|
||||
{
|
||||
StringSink sink(reserveSize);
|
||||
// the parser needs two extra bytes to append terminating characters, other users will
|
||||
// not care very much about the extra memory.
|
||||
StringSink sink(reserveSize + 2);
|
||||
drainFD(fd, sink, block);
|
||||
return std::move(*sink.s);
|
||||
return std::move(sink.s);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1055,7 +1061,7 @@ std::pair<int, std::string> runProgram(RunOptions && options)
|
|||
status = e.status;
|
||||
}
|
||||
|
||||
return {status, std::move(*sink.s)};
|
||||
return {status, std::move(sink.s)};
|
||||
}
|
||||
|
||||
void runProgram2(const RunOptions & options)
|
||||
|
|
@ -1229,23 +1235,22 @@ void _interrupted()
|
|||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
template<class C> C tokenizeString(std::string_view s, const string & separators)
|
||||
template<class C> C tokenizeString(std::string_view s, std::string_view separators)
|
||||
{
|
||||
C result;
|
||||
string::size_type pos = s.find_first_not_of(separators, 0);
|
||||
while (pos != string::npos) {
|
||||
string::size_type end = s.find_first_of(separators, pos + 1);
|
||||
if (end == string::npos) end = s.size();
|
||||
string token(s, pos, end - pos);
|
||||
result.insert(result.end(), token);
|
||||
result.insert(result.end(), string(s, pos, end - pos));
|
||||
pos = s.find_first_not_of(separators, end);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template Strings tokenizeString(std::string_view s, const string & separators);
|
||||
template StringSet tokenizeString(std::string_view s, const string & separators);
|
||||
template vector<string> tokenizeString(std::string_view s, const string & separators);
|
||||
template Strings tokenizeString(std::string_view s, std::string_view separators);
|
||||
template StringSet tokenizeString(std::string_view s, std::string_view separators);
|
||||
template vector<string> tokenizeString(std::string_view s, std::string_view separators);
|
||||
|
||||
|
||||
string chomp(std::string_view s)
|
||||
|
|
@ -1339,9 +1344,11 @@ std::string toLower(const std::string & s)
|
|||
}
|
||||
|
||||
|
||||
std::string shellEscape(const std::string & s)
|
||||
std::string shellEscape(const std::string_view s)
|
||||
{
|
||||
std::string r = "'";
|
||||
std::string r;
|
||||
r.reserve(s.size() + 2);
|
||||
r += "'";
|
||||
for (auto & i : s)
|
||||
if (i == '\'') r += "'\\''"; else r += i;
|
||||
r += '\'';
|
||||
|
|
@ -1746,7 +1753,7 @@ void bind(int fd, const std::string & path)
|
|||
|
||||
if (path.size() + 1 >= sizeof(addr.sun_path)) {
|
||||
Pid pid = startProcess([&]() {
|
||||
auto dir = dirOf(path);
|
||||
Path dir = dirOf(path);
|
||||
if (chdir(dir.c_str()) == -1)
|
||||
throw SysError("chdir to '%s' failed", dir);
|
||||
std::string base(baseNameOf(path));
|
||||
|
|
@ -1775,7 +1782,7 @@ void connect(int fd, const std::string & path)
|
|||
|
||||
if (path.size() + 1 >= sizeof(addr.sun_path)) {
|
||||
Pid pid = startProcess([&]() {
|
||||
auto dir = dirOf(path);
|
||||
Path dir = dirOf(path);
|
||||
if (chdir(dir.c_str()) == -1)
|
||||
throw SysError("chdir to '%s' failed", dir);
|
||||
std::string base(baseNameOf(path));
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
|
|
@ -47,30 +49,32 @@ void clearEnv();
|
|||
specified directory, or the current directory otherwise. The path
|
||||
is also canonicalised. */
|
||||
Path absPath(Path path,
|
||||
std::optional<Path> dir = {},
|
||||
std::optional<PathView> dir = {},
|
||||
bool resolveSymlinks = false);
|
||||
|
||||
/* Canonicalise a path by removing all `.' or `..' components and
|
||||
double or trailing slashes. Optionally resolves all symlink
|
||||
components such that each component of the resulting path is *not*
|
||||
a symbolic link. */
|
||||
Path canonPath(const Path & path, bool resolveSymlinks = false);
|
||||
Path canonPath(PathView path, bool resolveSymlinks = false);
|
||||
|
||||
/* Return the directory part of the given canonical path, i.e.,
|
||||
everything before the final `/'. If the path is the root or an
|
||||
immediate child thereof (e.g., `/foo'), this means `/'
|
||||
is returned.*/
|
||||
Path dirOf(const Path & path);
|
||||
Path dirOf(const PathView path);
|
||||
|
||||
/* Return the base name of the given canonical path, i.e., everything
|
||||
following the final `/' (trailing slashes are removed). */
|
||||
std::string_view baseNameOf(std::string_view path);
|
||||
|
||||
/* Check whether 'path' is a descendant of 'dir'. */
|
||||
bool isInDir(const Path & path, const Path & dir);
|
||||
/* Check whether 'path' is a descendant of 'dir'. Both paths must be
|
||||
canonicalized. */
|
||||
bool isInDir(std::string_view path, std::string_view dir);
|
||||
|
||||
/* Check whether 'path' is equal to 'dir' or a descendant of 'dir'. */
|
||||
bool isDirOrInDir(const Path & path, const Path & dir);
|
||||
/* Check whether 'path' is equal to 'dir' or a descendant of
|
||||
'dir'. Both paths must be canonicalized. */
|
||||
bool isDirOrInDir(std::string_view path, std::string_view dir);
|
||||
|
||||
/* Get status of `path'. */
|
||||
struct stat lstat(const Path & path);
|
||||
|
|
@ -144,6 +148,9 @@ Path getDataDir();
|
|||
/* Create a directory and all its parents, if necessary. Returns the
|
||||
list of created directories, in order of creation. */
|
||||
Paths createDirs(const Path & path);
|
||||
inline Paths createDirs(PathView path) {
|
||||
return createDirs(Path(path));
|
||||
}
|
||||
|
||||
/* Create a symlink. */
|
||||
void createSymlink(const Path & target, const Path & link,
|
||||
|
|
@ -183,6 +190,7 @@ public:
|
|||
void cancel();
|
||||
void reset(const Path & p, bool recursive = true);
|
||||
operator Path() const { return path; }
|
||||
operator PathView() const { return path; }
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -364,15 +372,19 @@ MakeError(FormatError, Error);
|
|||
|
||||
|
||||
/* String tokenizer. */
|
||||
template<class C> C tokenizeString(std::string_view s, const string & separators = " \t\n\r");
|
||||
template<class C> C tokenizeString(std::string_view s, std::string_view separators = " \t\n\r");
|
||||
|
||||
|
||||
/* Concatenate the given strings with a separator between the
|
||||
elements. */
|
||||
template<class C>
|
||||
string concatStringsSep(const string & sep, const C & ss)
|
||||
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();
|
||||
string s;
|
||||
s.reserve(size);
|
||||
for (auto & i : ss) {
|
||||
if (s.size() != 0) s += sep;
|
||||
s += i;
|
||||
|
|
@ -380,6 +392,14 @@ string concatStringsSep(const string & sep, const C & ss)
|
|||
return s;
|
||||
}
|
||||
|
||||
template<class ... Parts>
|
||||
auto concatStrings(Parts && ... parts)
|
||||
-> std::enable_if_t<(... && std::is_convertible_v<Parts, std::string_view>), string>
|
||||
{
|
||||
std::string_view views[sizeof...(parts)] = { parts... };
|
||||
return concatStringsSep({}, views);
|
||||
}
|
||||
|
||||
|
||||
/* Add quotes around a collection of strings. */
|
||||
template<class C> Strings quoteStrings(const C & c)
|
||||
|
|
@ -417,21 +437,21 @@ bool statusOk(int status);
|
|||
|
||||
/* Parse a string into an integer. */
|
||||
template<class N>
|
||||
std::optional<N> string2Int(const std::string & s)
|
||||
std::optional<N> string2Int(const std::string_view s)
|
||||
{
|
||||
if (s.substr(0, 1) == "-" && !std::numeric_limits<N>::is_signed)
|
||||
return std::nullopt;
|
||||
std::istringstream str(s);
|
||||
N n;
|
||||
str >> n;
|
||||
if (str && str.get() == EOF) return n;
|
||||
return std::nullopt;
|
||||
try {
|
||||
return boost::lexical_cast<N>(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<class N>
|
||||
N string2IntWithUnitPrefix(std::string s)
|
||||
N string2IntWithUnitPrefix(std::string_view s)
|
||||
{
|
||||
N multiplier = 1;
|
||||
if (!s.empty()) {
|
||||
|
|
@ -442,7 +462,7 @@ N string2IntWithUnitPrefix(std::string s)
|
|||
else if (u == 'G') multiplier = 1ULL << 30;
|
||||
else if (u == 'T') multiplier = 1ULL << 40;
|
||||
else throw UsageError("invalid unit specifier '%1%'", u);
|
||||
s.resize(s.size() - 1);
|
||||
s.remove_suffix(1);
|
||||
}
|
||||
}
|
||||
if (auto n = string2Int<N>(s))
|
||||
|
|
@ -452,13 +472,13 @@ N string2IntWithUnitPrefix(std::string s)
|
|||
|
||||
/* Parse a string into a float. */
|
||||
template<class N>
|
||||
std::optional<N> string2Float(const string & s)
|
||||
std::optional<N> string2Float(const std::string_view s)
|
||||
{
|
||||
std::istringstream str(s);
|
||||
N n;
|
||||
str >> n;
|
||||
if (str && str.get() == EOF) return n;
|
||||
return std::nullopt;
|
||||
try {
|
||||
return boost::lexical_cast<N>(s.data(), s.size());
|
||||
} catch (const boost::bad_lexical_cast &) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -475,7 +495,7 @@ std::string toLower(const std::string & s);
|
|||
|
||||
|
||||
/* Escape a string as a shell word. */
|
||||
std::string shellEscape(const std::string & s);
|
||||
std::string shellEscape(const std::string_view s);
|
||||
|
||||
|
||||
/* Exception handling in destructors: print an error message, then
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue