1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-30 14:10:59 +01:00

Merge remote-tracking branch 'origin/master' into lazy-trees

This commit is contained in:
Eelco Dolstra 2022-05-09 11:26:39 +02:00
commit 30ca717558
175 changed files with 4032 additions and 1936 deletions

View file

@ -127,11 +127,11 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
if (flag.handler.arity == ArityAny) break;
throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity);
}
if (flag.completer)
if (auto prefix = needsCompletion(*pos)) {
anyCompleted = true;
if (auto prefix = needsCompletion(*pos)) {
anyCompleted = true;
if (flag.completer)
flag.completer(n, *prefix);
}
}
args.push_back(*pos++);
}
if (!anyCompleted)
@ -146,6 +146,7 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
&& hasPrefix(name, std::string(*prefix, 2)))
completions->add("--" + name, flag->description);
}
return false;
}
auto i = longFlags.find(std::string(*pos, 2));
if (i == longFlags.end()) return false;
@ -187,10 +188,12 @@ bool Args::processArgs(const Strings & args, bool finish)
{
std::vector<std::string> ss;
for (const auto &[n, s] : enumerate(args)) {
ss.push_back(s);
if (exp.completer)
if (auto prefix = needsCompletion(s))
if (auto prefix = needsCompletion(s)) {
ss.push_back(*prefix);
if (exp.completer)
exp.completer(n, *prefix);
} else
ss.push_back(s);
}
exp.handler.fun(ss);
expectedArgs.pop_front();
@ -279,21 +282,22 @@ static void _completePath(std::string_view prefix, bool onlyDirs)
{
completionType = ctFilenames;
glob_t globbuf;
int flags = GLOB_NOESCAPE | GLOB_TILDE;
int flags = GLOB_NOESCAPE;
#ifdef GLOB_ONLYDIR
if (onlyDirs)
flags |= GLOB_ONLYDIR;
#endif
if (glob((std::string(prefix) + "*").c_str(), flags, nullptr, &globbuf) == 0) {
// using expandTilde here instead of GLOB_TILDE(_CHECK) so that ~<Tab> expands to /home/user/
if (glob((expandTilde(prefix) + "*").c_str(), flags, nullptr, &globbuf) == 0) {
for (size_t i = 0; i < globbuf.gl_pathc; ++i) {
if (onlyDirs) {
auto st = lstat(globbuf.gl_pathv[i]);
auto st = stat(globbuf.gl_pathv[i]);
if (!S_ISDIR(st.st_mode)) continue;
}
completions->add(globbuf.gl_pathv[i]);
}
globfree(&globbuf);
}
globfree(&globbuf);
}
void completePath(size_t, std::string_view prefix)
@ -322,11 +326,6 @@ MultiCommand::MultiCommand(const Commands & commands_)
.optional = true,
.handler = {[=](std::string s) {
assert(!command);
if (auto prefix = needsCompletion(s)) {
for (auto & [name, command] : commands)
if (hasPrefix(name, *prefix))
completions->add(name);
}
auto i = commands.find(s);
if (i == commands.end()) {
std::set<std::string> commandNames;
@ -337,6 +336,11 @@ MultiCommand::MultiCommand(const Commands & commands_)
}
command = {s, i->second()};
command->second->parent = this;
}},
.completer = {[&](size_t, std::string_view prefix) {
for (auto & [name, command] : commands)
if (hasPrefix(name, prefix))
completions->add(name);
}}
});

View file

@ -0,0 +1,68 @@
#pragma once
#include <cstdint>
#include <cstdlib>
#include <vector>
#include <limits>
namespace nix {
/* Provides an indexable container like vector<> with memory overhead
guarantees like list<> by allocating storage in chunks of ChunkSize
elements instead of using a contiguous memory allocation like vector<>
does. Not using a single vector that is resized reduces memory overhead
on large data sets by on average (growth factor)/2, mostly
eliminates copies within the vector during resizing, and provides stable
references to its elements. */
template<typename T, size_t ChunkSize>
class ChunkedVector {
private:
uint32_t size_ = 0;
std::vector<std::vector<T>> chunks;
/* keep this out of the ::add hot path */
[[gnu::noinline]]
auto & addChunk()
{
if (size_ >= std::numeric_limits<uint32_t>::max() - ChunkSize)
abort();
chunks.emplace_back();
chunks.back().reserve(ChunkSize);
return chunks.back();
}
public:
ChunkedVector(uint32_t reserve)
{
chunks.reserve(reserve);
addChunk();
}
uint32_t size() const { return size_; }
std::pair<T &, uint32_t> add(T value)
{
const auto idx = size_++;
auto & chunk = [&] () -> auto & {
if (auto & back = chunks.back(); back.size() < ChunkSize)
return back;
return addChunk();
}();
auto & result = chunk.emplace_back(std::move(value));
return {result, idx};
}
const T & operator[](uint32_t idx) const
{
return chunks[idx / ChunkSize][idx % ChunkSize];
}
template<typename Fn>
void forEach(Fn fn) const
{
for (const auto & c : chunks)
for (const auto & e : c)
fn(e);
}
};
}

View file

@ -9,10 +9,9 @@ namespace nix {
const std::string nativeSystem = SYSTEM;
BaseError & BaseError::addTrace(std::optional<ErrPos> e, hintformat hint)
void BaseError::addTrace(std::optional<ErrPos> e, hintformat hint)
{
err.traces.push_front(Trace { .pos = e, .hint = hint });
return *this;
}
// c++ std::exception descendants must have a 'const char* what()' function.
@ -22,12 +21,9 @@ const std::string & BaseError::calcWhat() const
if (what_.has_value())
return *what_;
else {
err.name = sname();
std::ostringstream oss;
showErrorInfo(oss, err, loggerSettings.showTrace);
what_ = oss.str();
return *what_;
}
}

View file

@ -87,11 +87,7 @@ struct ErrPos {
origin = pos.origin;
line = pos.line;
column = pos.column;
// is file symbol null?
if (pos.file.set())
file = pos.file;
else
file = "";
file = pos.file;
return *this;
}
@ -109,7 +105,6 @@ struct Trace {
struct ErrorInfo {
Verbosity level;
std::string name; // FIXME: rename
hintformat msg;
std::optional<ErrPos> errPos;
std::list<Trace> traces;
@ -162,8 +157,6 @@ public:
: err(e)
{ }
virtual const char* sname() const { return "BaseError"; }
#ifdef EXCEPTION_NEEDS_THROW_SPEC
~BaseError() throw () { };
const char * what() const throw () { return calcWhat().c_str(); }
@ -175,12 +168,12 @@ public:
const ErrorInfo & info() const { calcWhat(); return err; }
template<typename... Args>
BaseError & addTrace(std::optional<ErrPos> e, const std::string & fs, const Args & ... args)
void addTrace(std::optional<ErrPos> e, const std::string & fs, const Args & ... args)
{
return addTrace(e, hintfmt(fs, args...));
addTrace(e, hintfmt(fs, args...));
}
BaseError & addTrace(std::optional<ErrPos> e, hintformat hint);
void addTrace(std::optional<ErrPos> e, hintformat hint);
bool hasTrace() const { return !err.traces.empty(); }
};
@ -190,7 +183,6 @@ public:
{ \
public: \
using superClass::superClass; \
virtual const char* sname() const override { return #newClass; } \
}
MakeError(Error, BaseError);
@ -210,8 +202,6 @@ public:
auto hf = hintfmt(args...);
err.msg = hintfmt("%1%: %2%", normaltxt(hf.str()), strerror(errNo));
}
virtual const char* sname() const override { return "SysError"; }
};
}

View file

@ -7,6 +7,7 @@ namespace nix {
std::map<ExperimentalFeature, std::string> stringifiedXpFeatures = {
{ Xp::CaDerivations, "ca-derivations" },
{ Xp::ImpureDerivations, "impure-derivations" },
{ Xp::Flakes, "flakes" },
{ Xp::NixCommand, "nix-command" },
{ Xp::RecursiveNix, "recursive-nix" },
@ -34,7 +35,9 @@ const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::str
std::string_view showExperimentalFeature(const ExperimentalFeature feature)
{
return stringifiedXpFeatures.at(feature);
const auto ret = get(stringifiedXpFeatures, feature);
assert(ret);
return *ret;
}
std::set<ExperimentalFeature> parseFeatures(const std::set<std::string> & rawFeatures)
@ -57,4 +60,20 @@ std::ostream & operator <<(std::ostream & str, const ExperimentalFeature & featu
return str << showExperimentalFeature(feature);
}
void to_json(nlohmann::json & j, const ExperimentalFeature & feature)
{
j = showExperimentalFeature(feature);
}
void from_json(const nlohmann::json & j, ExperimentalFeature & feature)
{
const std::string input = j;
const auto parsed = parseExperimentalFeature(input);
if (parsed.has_value())
feature = *parsed;
else
throw Error("Unknown experimental feature '%s' in JSON input", input);
}
}

View file

@ -16,6 +16,7 @@ namespace nix {
enum struct ExperimentalFeature
{
CaDerivations,
ImpureDerivations,
Flakes,
NixCommand,
RecursiveNix,
@ -48,10 +49,13 @@ public:
ExperimentalFeature missingFeature;
MissingExperimentalFeature(ExperimentalFeature);
virtual const char * sname() const override
{
return "MissingExperimentalFeature";
}
};
/**
* Semi-magic conversion to and from json.
* See the nlohmann/json readme for more details.
*/
void to_json(nlohmann::json &, const ExperimentalFeature &);
void from_json(const nlohmann::json &, ExperimentalFeature &);
}

View file

@ -2,7 +2,6 @@
#include <boost/format.hpp>
#include <string>
#include <regex>
#include "ansicolor.hh"
@ -155,15 +154,4 @@ inline hintformat hintfmt(std::string plain_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);
}

25
src/libutil/git.cc Normal file
View file

@ -0,0 +1,25 @@
#include "git.hh"
#include <regex>
namespace nix {
namespace git {
std::optional<LsRemoteRefLine> parseLsRemoteLine(std::string_view line)
{
const static std::regex line_regex("^(ref: *)?([^\\s]+)(?:\\t+(.*))?$");
std::match_results<std::string_view::const_iterator> match;
if (!std::regex_match(line.cbegin(), line.cend(), match, line_regex))
return std::nullopt;
return LsRemoteRefLine {
.kind = match[1].length() == 0
? LsRemoteRefLine::Kind::Object
: LsRemoteRefLine::Kind::Symbolic,
.target = match[2],
.reference = match[3].length() == 0 ? std::nullopt : std::optional<std::string>{ match[3] }
};
}
}
}

40
src/libutil/git.hh Normal file
View file

@ -0,0 +1,40 @@
#pragma once
#include <string>
#include <string_view>
#include <optional>
namespace nix {
namespace git {
// A line from the output of `git ls-remote --symref`.
//
// These can be of two kinds:
//
// - Symbolic references of the form
//
// ref: {target} {reference}
//
// where {target} is itself a reference and {reference} is optional
//
// - Object references of the form
//
// {target} {reference}
//
// where {target} is a commit id and {reference} is mandatory
struct LsRemoteRefLine {
enum struct Kind {
Symbolic,
Object
};
Kind kind;
std::string target;
std::optional<std::string> reference;
};
std::optional<LsRemoteRefLine> parseLsRemoteLine(std::string_view line);
}
}

View file

@ -155,7 +155,7 @@ static std::pair<std::optional<HashType>, bool> getParsedTypeAndSRI(std::string_
{
bool isSRI = false;
// Parse the has type before the separater, if there was one.
// Parse the hash type before the separator, if there was one.
std::optional<HashType> optParsedType;
{
auto hashRaw = splitPrefixTo(rest, ':');

View file

@ -93,13 +93,11 @@ public:
std::string gitRev() const
{
assert(type == htSHA1);
return to_string(Base16, false);
}
std::string gitShortRev() const
{
assert(type == htSHA1);
return std::string(to_string(Base16, false), 0, 7);
}

View file

@ -1,6 +1,4 @@
#include "fmt.hh"
#include <regex>
#include "hilite.hh"
namespace nix {

20
src/libutil/hilite.hh Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include <regex>
#include <vector>
#include <string>
namespace nix {
/* 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);
}

21
src/libutil/json-utils.hh Normal file
View file

@ -0,0 +1,21 @@
#pragma once
#include <nlohmann/json.hpp>
namespace nix {
const nlohmann::json * get(const nlohmann::json & map, const std::string & key)
{
auto i = map.find(key);
if (i == map.end()) return nullptr;
return &*i;
}
nlohmann::json * get(nlohmann::json & map, const std::string & key)
{
auto i = map.find(key);
if (i == map.end()) return nullptr;
return &*i;
}
}

View file

@ -99,47 +99,4 @@ make_ref(Args&&... args)
return ref<T>(p);
}
/* A non-nullable pointer.
This is similar to a C++ "& reference", but mutable.
This is similar to ref<T> but backed by a regular pointer instead of a smart pointer.
*/
template<typename T>
class ptr {
private:
T * p;
public:
ptr<T>(const ptr<T> & r)
: p(r.p)
{ }
explicit ptr<T>(T * p)
: p(p)
{
if (!p)
throw std::invalid_argument("null pointer cast to ptr");
}
T* operator ->() const
{
return &*p;
}
T& operator *() const
{
return *p;
}
bool operator == (const ptr<T> & other) const
{
return p == other.p;
}
bool operator != (const ptr<T> & other) const
{
return p != other.p;
}
};
}

View file

@ -357,7 +357,7 @@ Sink & operator << (Sink & sink, const Error & ex)
sink
<< "Error"
<< info.level
<< info.name
<< "Error" // removed
<< info.msg.str()
<< 0 // FIXME: info.errPos
<< info.traces.size();
@ -426,11 +426,10 @@ Error readError(Source & source)
auto type = readString(source);
assert(type == "Error");
auto level = (Verbosity) readInt(source);
auto name = readString(source);
auto name = readString(source); // removed
auto msg = readString(source);
ErrorInfo info {
.level = level,
.name = name,
.msg = hintformat(std::move(format("%s") % msg)),
};
auto havePos = readNum<size_t>(source);

View file

@ -0,0 +1,54 @@
#include "chunked-vector.hh"
#include <gtest/gtest.h>
namespace nix {
TEST(ChunkedVector, InitEmpty) {
auto v = ChunkedVector<int, 2>(100);
ASSERT_EQ(v.size(), 0);
}
TEST(ChunkedVector, GrowsCorrectly) {
auto v = ChunkedVector<int, 2>(100);
for (auto i = 1; i < 20; i++) {
v.add(i);
ASSERT_EQ(v.size(), i);
}
}
TEST(ChunkedVector, AddAndGet) {
auto v = ChunkedVector<int, 2>(100);
for (auto i = 1; i < 20; i++) {
auto [i2, idx] = v.add(i);
auto & i3 = v[idx];
ASSERT_EQ(i, i2);
ASSERT_EQ(&i2, &i3);
}
}
TEST(ChunkedVector, ForEach) {
auto v = ChunkedVector<int, 2>(100);
for (auto i = 1; i < 20; i++) {
v.add(i);
}
int count = 0;
v.forEach([&count](int elt) {
count++;
});
ASSERT_EQ(count, v.size());
}
TEST(ChunkedVector, OverflowOK) {
// Similar to the AddAndGet, but intentionnally use a small
// initial ChunkedVector to force it to overflow
auto v = ChunkedVector<int, 2>(2);
for (auto i = 1; i < 20; i++) {
auto [i2, idx] = v.add(i);
auto & i3 = v[idx];
ASSERT_EQ(i, i2);
ASSERT_EQ(&i2, &i3);
}
}
}

33
src/libutil/tests/git.cc Normal file
View file

@ -0,0 +1,33 @@
#include "git.hh"
#include <gtest/gtest.h>
namespace nix {
TEST(GitLsRemote, parseSymrefLineWithReference) {
auto line = "ref: refs/head/main HEAD";
auto res = git::parseLsRemoteLine(line);
ASSERT_TRUE(res.has_value());
ASSERT_EQ(res->kind, git::LsRemoteRefLine::Kind::Symbolic);
ASSERT_EQ(res->target, "refs/head/main");
ASSERT_EQ(res->reference, "HEAD");
}
TEST(GitLsRemote, parseSymrefLineWithNoReference) {
auto line = "ref: refs/head/main";
auto res = git::parseLsRemoteLine(line);
ASSERT_TRUE(res.has_value());
ASSERT_EQ(res->kind, git::LsRemoteRefLine::Kind::Symbolic);
ASSERT_EQ(res->target, "refs/head/main");
ASSERT_EQ(res->reference, std::nullopt);
}
TEST(GitLsRemote, parseObjectRefLine) {
auto line = "abc123 refs/head/main";
auto res = git::parseLsRemoteLine(line);
ASSERT_TRUE(res.has_value());
ASSERT_EQ(res->kind, git::LsRemoteRefLine::Kind::Object);
ASSERT_EQ(res->target, "abc123");
ASSERT_EQ(res->reference, "refs/head/main");
}
}

View file

@ -1,9 +1,7 @@
#include "fmt.hh"
#include "hilite.hh"
#include <gtest/gtest.h>
#include <regex>
namespace nix {
/* ----------- tests for fmt.hh -------------------------------------------------*/

View file

@ -584,7 +584,7 @@ namespace nix {
TEST(get, emptyContainer) {
StringMap s = { };
auto expected = std::nullopt;
auto expected = nullptr;
ASSERT_EQ(get(s, "one"), expected);
}
@ -595,7 +595,23 @@ namespace nix {
s["two"] = "er";
auto expected = "yi";
ASSERT_EQ(get(s, "one"), expected);
ASSERT_EQ(*get(s, "one"), expected);
}
TEST(getOr, emptyContainer) {
StringMap s = { };
auto expected = "yi";
ASSERT_EQ(getOr(s, "one", "yi"), expected);
}
TEST(getOr, getFromContainer) {
StringMap s;
s["one"] = "yi";
s["two"] = "er";
auto expected = "yi";
ASSERT_EQ(getOr(s, "one", "nope"), expected);
}
/* ----------------------------------------------------------------------------

View file

@ -178,7 +178,7 @@ namespace nix {
}
TEST(parseURL, parseFileURLWithQueryAndFragment) {
auto s = "file:///none/of/your/business";
auto s = "file:///none/of//your/business";
auto parsed = parseURL(s);
ParsedURL expected {
@ -186,7 +186,7 @@ namespace nix {
.base = "",
.scheme = "file",
.authority = "",
.path = "/none/of/your/business",
.path = "/none/of//your/business",
.query = (StringMap) { },
.fragment = "",
};

View file

@ -5,6 +5,7 @@
#include <list>
#include <set>
#include <string>
#include <limits>
#include <map>
#include <variant>
#include <vector>

View file

@ -18,7 +18,7 @@ const static std::string userRegex = "(?:(?:" + unreservedRegex + "|" + pctEncod
const static std::string authorityRegex = "(?:" + userRegex + "@)?" + hostRegex + "(?::[0-9]+)?";
const static std::string pcharRegex = "(?:" + unreservedRegex + "|" + pctEncoded + "|" + subdelimsRegex + "|[:@])";
const static std::string queryRegex = "(?:" + pcharRegex + "|[/? \"])*";
const static std::string segmentRegex = "(?:" + pcharRegex + "+)";
const static std::string segmentRegex = "(?:" + pcharRegex + "*)";
const static std::string absPathRegex = "(?:(?:/" + segmentRegex + ")*/?)";
const static std::string pathRegex = "(?:" + segmentRegex + "(?:/" + segmentRegex + ")*/?)";

View file

@ -71,13 +71,11 @@ void clearEnv()
unsetenv(name.first.c_str());
}
void replaceEnv(std::map<std::string, std::string> newEnv)
void replaceEnv(const std::map<std::string, std::string> & newEnv)
{
clearEnv();
for (auto newEnvVar : newEnv)
{
for (auto & newEnvVar : newEnv)
setenv(newEnvVar.first.c_str(), newEnvVar.second.c_str(), 1);
}
}
@ -200,6 +198,17 @@ std::string_view baseNameOf(std::string_view path)
}
std::string expandTilde(std::string_view path)
{
// TODO: expand ~user ?
auto tilde = path.substr(0, 2);
if (tilde == "~/" || tilde == "~")
return getHome() + std::string(path.substr(1));
else
return std::string(path);
}
bool isInDir(std::string_view path, std::string_view dir)
{
return path.substr(0, 1) == "/"
@ -215,6 +224,15 @@ bool isDirOrInDir(std::string_view path, std::string_view dir)
}
struct stat stat(const Path & path)
{
struct stat st;
if (stat(path.c_str(), &st))
throw SysError("getting status of '%1%'", path);
return st;
}
struct stat lstat(const Path & path)
{
struct stat st;
@ -1064,7 +1082,7 @@ std::string runProgram(Path program, bool searchPath, const Strings & args,
auto res = runProgram(RunOptions {.program = program, .searchPath = searchPath, .args = args, .input = input});
if (!statusOk(res.first))
throw ExecError(res.first, fmt("program '%1%' %2%", program, statusToString(res.first)));
throw ExecError(res.first, "program '%1%' %2%", program, statusToString(res.first));
return res.second;
}
@ -1192,7 +1210,7 @@ void runProgram2(const RunOptions & options)
if (source) promise.get_future().get();
if (status)
throw ExecError(status, fmt("program '%1%' %2%", options.program, statusToString(status)));
throw ExecError(status, "program '%1%' %2%", options.program, statusToString(status));
}
@ -1261,9 +1279,9 @@ template<class C> C tokenizeString(std::string_view s, std::string_view separato
{
C result;
auto pos = s.find_first_not_of(separators, 0);
while (pos != std::string::npos) {
while (pos != std::string_view::npos) {
auto end = s.find_first_of(separators, pos + 1);
if (end == std::string::npos) end = s.size();
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);
}
@ -1473,6 +1491,7 @@ constexpr char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv
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) {
@ -1504,6 +1523,9 @@ std::string base64Decode(std::string_view s)
}();
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) {
@ -1581,7 +1603,6 @@ std::pair<std::string_view, std::string_view> getLine(std::string_view s)
//////////////////////////////////////////////////////////////////////
static Sync<std::pair<unsigned short, unsigned short>> windowSize{{0, 0}};
@ -1705,7 +1726,9 @@ void setStackSize(size_t stackSize)
#endif
}
#if __linux__
static AutoCloseFD fdSavedMountNamespace;
#endif
void saveMountNamespace()
{
@ -1724,8 +1747,13 @@ void restoreMountNamespace()
{
#if __linux__
try {
auto savedCwd = absPath(".");
if (fdSavedMountNamespace && setns(fdSavedMountNamespace.get(), CLONE_NEWNS) == -1)
throw SysError("restoring parent mount namespace");
if (chdir(savedCwd.c_str()) == -1) {
throw SysError("restoring cwd");
}
} catch (Error & e) {
debug(e.msg());
}

View file

@ -68,6 +68,9 @@ Path dirOf(const PathView path);
following the final `/' (trailing slashes are removed). */
std::string_view baseNameOf(std::string_view path);
/* Perform tilde expansion on a path. */
std::string expandTilde(std::string_view path);
/* Check whether 'path' is a descendant of 'dir'. Both paths must be
canonicalized. */
bool isInDir(std::string_view path, std::string_view dir);
@ -77,6 +80,7 @@ bool isInDir(std::string_view path, std::string_view dir);
bool isDirOrInDir(std::string_view path, std::string_view dir);
/* Get status of `path'. */
struct stat stat(const Path & path);
struct stat lstat(const Path & path);
/* Return true iff the given path exists. */
@ -545,13 +549,31 @@ std::pair<std::string_view, std::string_view> getLine(std::string_view s);
/* Get a value for the specified key from an associate container. */
template <class T>
std::optional<typename T::mapped_type> get(const T & map, const typename T::key_type & key)
const typename T::mapped_type * get(const T & map, const typename T::key_type & key)
{
auto i = map.find(key);
if (i == map.end()) return {};
return std::optional<typename T::mapped_type>(i->second);
if (i == map.end()) return nullptr;
return &i->second;
}
template <class T>
typename T::mapped_type * get(T & map, const typename T::key_type & key)
{
auto i = map.find(key);
if (i == map.end()) return nullptr;
return &i->second;
}
/* Get a value for the specified key from an associate container, or a default value if the key isn't present. */
template <class T>
const typename T::mapped_type & getOr(T & map,
const typename T::key_type & key,
const typename T::mapped_type & defaultValue)
{
auto i = map.find(key);
if (i == map.end()) return defaultValue;
return i->second;
}
/* Remove and return the first item from a container. */
template <class T>