libexpr: standardize on strings for attr cache traversal
it's all strings anyway. the db stores strings, the cli wants to
interact with attr paths as strings, so we will just use strings
Change-Id: Id9ea07d92343de77e8d47af8fec1e86ae225e9a1
This commit is contained in:
parent
dd7d7450a5
commit
9a3e3a5560
|
@ -118,8 +118,8 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
|
|||
|
||||
std::optional<NixInt::Inner> priority;
|
||||
|
||||
if (attr->maybeGetAttr(state->s.outputSpecified)) {
|
||||
} else if (auto aMeta = attr->maybeGetAttr(state->s.meta)) {
|
||||
if (attr->maybeGetAttr("outputSpecified")) {
|
||||
} else if (auto aMeta = attr->maybeGetAttr("meta")) {
|
||||
if (auto aPriority = aMeta->maybeGetAttr("priority"))
|
||||
priority = aPriority->getInt().value;
|
||||
}
|
||||
|
@ -130,12 +130,12 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
|
|||
.outputs = std::visit(overloaded {
|
||||
[&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec {
|
||||
std::set<std::string> outputsToInstall;
|
||||
if (auto aOutputSpecified = attr->maybeGetAttr(state->s.outputSpecified)) {
|
||||
if (auto aOutputSpecified = attr->maybeGetAttr("outputSpecified")) {
|
||||
if (aOutputSpecified->getBool()) {
|
||||
if (auto aOutputName = attr->maybeGetAttr("outputName"))
|
||||
outputsToInstall = { aOutputName->getString() };
|
||||
}
|
||||
} else if (auto aMeta = attr->maybeGetAttr(state->s.meta)) {
|
||||
} else if (auto aMeta = attr->maybeGetAttr("meta")) {
|
||||
if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall"))
|
||||
for (auto & s : aOutputsToInstall->getListOfStrings())
|
||||
outputsToInstall.insert(s);
|
||||
|
@ -184,7 +184,7 @@ InstallableFlake::getCursors()
|
|||
for (auto & attrPath : attrPaths) {
|
||||
debug("trying flake output attribute '%s'", attrPath);
|
||||
|
||||
auto attr = root->findAlongAttrPath(parseAttrPath(*state, attrPath));
|
||||
auto attr = root->findAlongAttrPath(parseAttrPath(attrPath));
|
||||
if (attr) {
|
||||
res.push_back(ref(*attr));
|
||||
} else {
|
||||
|
|
|
@ -305,13 +305,13 @@ void completeFlakeRefWithFragment(
|
|||
attrPathPrefixes.push_back("");
|
||||
|
||||
for (auto & attrPathPrefixS : attrPathPrefixes) {
|
||||
auto attrPathPrefix = parseAttrPath(*evalState, attrPathPrefixS);
|
||||
auto attrPathPrefix = parseAttrPath(attrPathPrefixS);
|
||||
auto attrPathS = attrPathPrefixS + std::string(fragment);
|
||||
auto attrPath = parseAttrPath(*evalState, attrPathS);
|
||||
auto attrPath = parseAttrPath(attrPathS);
|
||||
|
||||
std::string lastAttr;
|
||||
if (!attrPath.empty() && !attrPathS.ends_with(".")) {
|
||||
lastAttr = evalState->symbols[attrPath.back()];
|
||||
lastAttr = attrPath.back();
|
||||
attrPath.pop_back();
|
||||
}
|
||||
|
||||
|
@ -319,11 +319,11 @@ void completeFlakeRefWithFragment(
|
|||
if (!attr) continue;
|
||||
|
||||
for (auto & attr2 : (*attr)->getAttrs()) {
|
||||
if (std::string_view(evalState->symbols[attr2]).starts_with(lastAttr)) {
|
||||
auto attrPath2 = (*attr)->getAttrPath(attr2);
|
||||
if (std::string_view attr2s = attr2; attr2s.starts_with(lastAttr)) {
|
||||
auto attrPath2 = (*attr)->getAttrPath(attr2s);
|
||||
/* Strip the attrpath prefix. */
|
||||
attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size());
|
||||
completions.add(flakeRefS + "#" + prefixRoot + concatStringsSep(".", evalState->symbols.resolve(attrPath2)));
|
||||
completions.add(flakeRefS + "#" + prefixRoot + concatStringsSep(".", attrPath2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -332,7 +332,7 @@ void completeFlakeRefWithFragment(
|
|||
attrpaths. */
|
||||
if (fragment.empty()) {
|
||||
for (auto & attrPath : defaultFlakeAttrPaths) {
|
||||
auto attr = root->findAlongAttrPath(parseAttrPath(*evalState, attrPath));
|
||||
auto attr = root->findAlongAttrPath(parseAttrPath(attrPath));
|
||||
if (!attr) continue;
|
||||
completions.add(flakeRefS + "#" + prefixRoot);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
namespace nix {
|
||||
|
||||
|
||||
static Strings parseAttrPath(std::string_view s)
|
||||
Strings parseAttrPath(std::string_view s)
|
||||
{
|
||||
Strings res;
|
||||
std::string cur;
|
||||
|
@ -31,15 +31,6 @@ static Strings parseAttrPath(std::string_view s)
|
|||
}
|
||||
|
||||
|
||||
std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s)
|
||||
{
|
||||
std::vector<Symbol> res;
|
||||
for (auto & a : parseAttrPath(s))
|
||||
res.push_back(state.symbols.create(a));
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::string & attrPath,
|
||||
Bindings & autoArgs, Value & vIn)
|
||||
{
|
||||
|
|
|
@ -22,6 +22,6 @@ std::pair<Value *, PosIdx> findAlongAttrPath(
|
|||
*/
|
||||
std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what);
|
||||
|
||||
std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s);
|
||||
Strings parseAttrPath(std::string_view s);
|
||||
|
||||
}
|
||||
|
|
|
@ -35,15 +35,11 @@ struct AttrDb
|
|||
|
||||
std::unique_ptr<Sync<State>> _state;
|
||||
|
||||
SymbolTable & symbols;
|
||||
|
||||
AttrDb(
|
||||
const Store & cfg,
|
||||
const Hash & fingerprint,
|
||||
SymbolTable & symbols)
|
||||
const Hash & fingerprint)
|
||||
: cfg(cfg)
|
||||
, _state(std::make_unique<Sync<State>>())
|
||||
, symbols(symbols)
|
||||
{
|
||||
auto state(_state->lock());
|
||||
|
||||
|
@ -98,7 +94,7 @@ struct AttrDb
|
|||
|
||||
AttrId setAttrs(
|
||||
AttrKey key,
|
||||
const std::vector<Symbol> & attrs)
|
||||
const fullattr_t & attrs)
|
||||
{
|
||||
return doSQLite([&]()
|
||||
{
|
||||
|
@ -106,17 +102,17 @@ struct AttrDb
|
|||
|
||||
state->insertAttribute.use()
|
||||
(key.first)
|
||||
(symbols[key.second])
|
||||
(key.second)
|
||||
(AttrType::FullAttrs)
|
||||
(0, false).exec();
|
||||
|
||||
AttrId rowId = state->db.getLastInsertedRowId();
|
||||
assert(rowId);
|
||||
|
||||
for (auto & attr : attrs)
|
||||
for (auto & attr : attrs.p)
|
||||
state->insertAttribute.use()
|
||||
(rowId)
|
||||
(symbols[attr])
|
||||
(attr)
|
||||
(AttrType::Placeholder)
|
||||
(0, false).exec();
|
||||
|
||||
|
@ -141,14 +137,14 @@ struct AttrDb
|
|||
}
|
||||
state->insertAttributeWithContext.use()
|
||||
(key.first)
|
||||
(symbols[key.second])
|
||||
(key.second)
|
||||
(AttrType::String)
|
||||
(s)
|
||||
(ctx).exec();
|
||||
} else {
|
||||
state->insertAttribute.use()
|
||||
(key.first)
|
||||
(symbols[key.second])
|
||||
(key.second)
|
||||
(AttrType::String)
|
||||
(s).exec();
|
||||
}
|
||||
|
@ -167,7 +163,7 @@ struct AttrDb
|
|||
|
||||
state->insertAttribute.use()
|
||||
(key.first)
|
||||
(symbols[key.second])
|
||||
(key.second)
|
||||
(AttrType::Bool)
|
||||
(b ? 1 : 0).exec();
|
||||
|
||||
|
@ -185,7 +181,7 @@ struct AttrDb
|
|||
|
||||
state->insertAttribute.use()
|
||||
(key.first)
|
||||
(symbols[key.second])
|
||||
(key.second)
|
||||
(AttrType::Int)
|
||||
(n).exec();
|
||||
|
||||
|
@ -203,7 +199,7 @@ struct AttrDb
|
|||
|
||||
state->insertAttribute.use()
|
||||
(key.first)
|
||||
(symbols[key.second])
|
||||
(key.second)
|
||||
(AttrType::ListOfStrings)
|
||||
(concatStringsSep("\t", l)).exec();
|
||||
|
||||
|
@ -219,7 +215,7 @@ struct AttrDb
|
|||
|
||||
state->insertAttribute.use()
|
||||
(key.first)
|
||||
(symbols[key.second])
|
||||
(key.second)
|
||||
(AttrType::Placeholder)
|
||||
(0, false).exec();
|
||||
|
||||
|
@ -235,7 +231,7 @@ struct AttrDb
|
|||
|
||||
state->insertAttribute.use()
|
||||
(key.first)
|
||||
(symbols[key.second])
|
||||
(key.second)
|
||||
(AttrType::Missing)
|
||||
(0, false).exec();
|
||||
|
||||
|
@ -251,7 +247,7 @@ struct AttrDb
|
|||
|
||||
state->insertAttribute.use()
|
||||
(key.first)
|
||||
(symbols[key.second])
|
||||
(key.second)
|
||||
(AttrType::Misc)
|
||||
(0, false).exec();
|
||||
|
||||
|
@ -267,7 +263,7 @@ struct AttrDb
|
|||
|
||||
state->insertAttribute.use()
|
||||
(key.first)
|
||||
(symbols[key.second])
|
||||
(key.second)
|
||||
(AttrType::Failed)
|
||||
(0, false).exec();
|
||||
|
||||
|
@ -279,7 +275,7 @@ struct AttrDb
|
|||
{
|
||||
auto state(_state->lock());
|
||||
|
||||
auto queryAttribute(state->queryAttribute.use()(key.first)(symbols[key.second]));
|
||||
auto queryAttribute(state->queryAttribute.use()(key.first)(key.second));
|
||||
if (!queryAttribute.next()) return {};
|
||||
|
||||
auto rowId = (AttrId) queryAttribute.getInt(0);
|
||||
|
@ -290,10 +286,10 @@ struct AttrDb
|
|||
return {{rowId, placeholder_t()}};
|
||||
case AttrType::FullAttrs: {
|
||||
// FIXME: expensive, should separate this out.
|
||||
std::vector<Symbol> attrs;
|
||||
fullattr_t attrs;
|
||||
auto queryAttributes(state->queryAttributes.use()(rowId));
|
||||
while (queryAttributes.next())
|
||||
attrs.emplace_back(symbols.create(queryAttributes.getStr(0)));
|
||||
attrs.p.emplace_back(queryAttributes.getStr(0));
|
||||
return {{rowId, attrs}};
|
||||
}
|
||||
case AttrType::String: {
|
||||
|
@ -323,11 +319,10 @@ struct AttrDb
|
|||
|
||||
static std::shared_ptr<AttrDb> makeAttrDb(
|
||||
const Store & cfg,
|
||||
const Hash & fingerprint,
|
||||
SymbolTable & symbols)
|
||||
const Hash & fingerprint)
|
||||
{
|
||||
try {
|
||||
return std::make_shared<AttrDb>(cfg, fingerprint, symbols);
|
||||
return std::make_shared<AttrDb>(cfg, fingerprint);
|
||||
} catch (SQLiteError &) {
|
||||
ignoreExceptionExceptInterrupt();
|
||||
return nullptr;
|
||||
|
@ -348,7 +343,7 @@ EvalCache::EvalCache(
|
|||
std::optional<std::reference_wrapper<const Hash>> useCache,
|
||||
EvalState & state,
|
||||
RootLoader rootLoader)
|
||||
: db(useCache ? makeAttrDb(*state.store, *useCache, state.symbols) : nullptr)
|
||||
: db(useCache ? makeAttrDb(*state.store, *useCache) : nullptr)
|
||||
, state(state)
|
||||
, rootLoader(rootLoader)
|
||||
{
|
||||
|
@ -382,7 +377,7 @@ AttrCursor::AttrCursor(
|
|||
AttrKey AttrCursor::getKey()
|
||||
{
|
||||
if (!parent)
|
||||
return {0, root->state.s.epsilon};
|
||||
return {0, ""};
|
||||
if (!parent->first->cachedValue) {
|
||||
parent->first->cachedValue = root->db->getAttr(parent->first->getKey());
|
||||
assert(parent->first->cachedValue);
|
||||
|
@ -396,7 +391,7 @@ Value & AttrCursor::getValue()
|
|||
if (parent) {
|
||||
auto & vParent = parent->first->getValue();
|
||||
root->state.forceAttrs(vParent, noPos, "while searching for an attribute");
|
||||
auto attr = vParent.attrs->get(parent->second);
|
||||
auto attr = vParent.attrs->get(root->state.symbols.create(parent->second));
|
||||
if (!attr)
|
||||
throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr());
|
||||
_value = allocRootValue(attr->value);
|
||||
|
@ -406,7 +401,7 @@ Value & AttrCursor::getValue()
|
|||
return **_value;
|
||||
}
|
||||
|
||||
std::vector<Symbol> AttrCursor::getAttrPath() const
|
||||
std::vector<std::string> AttrCursor::getAttrPath() const
|
||||
{
|
||||
if (parent) {
|
||||
auto attrPath = parent->first->getAttrPath();
|
||||
|
@ -416,21 +411,21 @@ std::vector<Symbol> AttrCursor::getAttrPath() const
|
|||
return {};
|
||||
}
|
||||
|
||||
std::vector<Symbol> AttrCursor::getAttrPath(Symbol name) const
|
||||
std::vector<std::string> AttrCursor::getAttrPath(std::string_view name) const
|
||||
{
|
||||
auto attrPath = getAttrPath();
|
||||
attrPath.push_back(name);
|
||||
attrPath.emplace_back(name);
|
||||
return attrPath;
|
||||
}
|
||||
|
||||
std::string AttrCursor::getAttrPathStr() const
|
||||
{
|
||||
return concatStringsSep(".", root->state.symbols.resolve(getAttrPath()));
|
||||
return concatStringsSep(".", getAttrPath());
|
||||
}
|
||||
|
||||
std::string AttrCursor::getAttrPathStr(Symbol name) const
|
||||
std::string AttrCursor::getAttrPathStr(std::string_view name) const
|
||||
{
|
||||
return concatStringsSep(".", root->state.symbols.resolve(getAttrPath(name)));
|
||||
return concatStringsSep(".", getAttrPath(name));
|
||||
}
|
||||
|
||||
Value & AttrCursor::forceValue()
|
||||
|
@ -469,27 +464,23 @@ Value & AttrCursor::forceValue()
|
|||
return v;
|
||||
}
|
||||
|
||||
Suggestions AttrCursor::getSuggestionsForAttr(Symbol name)
|
||||
Suggestions AttrCursor::getSuggestionsForAttr(const std::string & name)
|
||||
{
|
||||
auto attrNames = getAttrs();
|
||||
std::set<std::string> strAttrNames;
|
||||
for (auto & name : attrNames)
|
||||
strAttrNames.insert(root->state.symbols[name]);
|
||||
|
||||
return Suggestions::bestMatches(strAttrNames, root->state.symbols[name]);
|
||||
return Suggestions::bestMatches({attrNames.begin(), attrNames.end()}, name);
|
||||
}
|
||||
|
||||
std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name)
|
||||
std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(const std::string & name)
|
||||
{
|
||||
if (root->db) {
|
||||
if (!cachedValue)
|
||||
cachedValue = root->db->getAttr(getKey());
|
||||
|
||||
if (cachedValue) {
|
||||
if (auto attrs = std::get_if<std::vector<Symbol>>(&cachedValue->second)) {
|
||||
for (auto & attr : *attrs)
|
||||
if (auto attrs = std::get_if<fullattr_t>(&cachedValue->second)) {
|
||||
for (auto & attr : attrs->p)
|
||||
if (attr == name)
|
||||
return std::make_shared<AttrCursor>(root, std::make_pair(shared_from_this(), attr));
|
||||
return std::make_shared<AttrCursor>(root, std::make_pair(shared_from_this(), name));
|
||||
return nullptr;
|
||||
} else if (std::get_if<placeholder_t>(&cachedValue->second)) {
|
||||
auto attr = root->db->getAttr({cachedValue->first, name});
|
||||
|
@ -516,7 +507,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name)
|
|||
return nullptr;
|
||||
//errors.make<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
|
||||
|
||||
auto attr = v.attrs->get(name);
|
||||
auto attr = v.attrs->get(root->state.symbols.create(name));
|
||||
|
||||
if (!attr) {
|
||||
if (root->db) {
|
||||
|
@ -538,12 +529,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name)
|
|||
root, std::make_pair(shared_from_this(), name), attr->value, std::move(cachedValue2));
|
||||
}
|
||||
|
||||
std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(std::string_view name)
|
||||
{
|
||||
return maybeGetAttr(root->state.symbols.create(name));
|
||||
}
|
||||
|
||||
ref<AttrCursor> AttrCursor::getAttr(Symbol name)
|
||||
ref<AttrCursor> AttrCursor::getAttr(const std::string & name)
|
||||
{
|
||||
auto p = maybeGetAttr(name);
|
||||
if (!p)
|
||||
|
@ -551,12 +537,7 @@ ref<AttrCursor> AttrCursor::getAttr(Symbol name)
|
|||
return ref(p);
|
||||
}
|
||||
|
||||
ref<AttrCursor> AttrCursor::getAttr(std::string_view name)
|
||||
{
|
||||
return getAttr(root->state.symbols.create(name));
|
||||
}
|
||||
|
||||
OrSuggestions<ref<AttrCursor>> AttrCursor::findAlongAttrPath(const std::vector<Symbol> & attrPath)
|
||||
OrSuggestions<ref<AttrCursor>> AttrCursor::findAlongAttrPath(const Strings & attrPath)
|
||||
{
|
||||
auto res = shared_from_this();
|
||||
for (auto & attr : attrPath) {
|
||||
|
@ -717,15 +698,15 @@ std::vector<std::string> AttrCursor::getListOfStrings()
|
|||
return res;
|
||||
}
|
||||
|
||||
std::vector<Symbol> AttrCursor::getAttrs()
|
||||
std::vector<std::string> AttrCursor::getAttrs()
|
||||
{
|
||||
if (root->db) {
|
||||
if (!cachedValue)
|
||||
cachedValue = root->db->getAttr(getKey());
|
||||
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
|
||||
if (auto attrs = std::get_if<std::vector<Symbol>>(&cachedValue->second)) {
|
||||
if (auto attrs = std::get_if<fullattr_t>(&cachedValue->second)) {
|
||||
debug("using cached attrset attribute '%s'", getAttrPathStr());
|
||||
return *attrs;
|
||||
return attrs->p;
|
||||
} else
|
||||
root->state.errors.make<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
|
||||
}
|
||||
|
@ -736,18 +717,15 @@ std::vector<Symbol> AttrCursor::getAttrs()
|
|||
if (v.type() != nAttrs)
|
||||
root->state.errors.make<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
|
||||
|
||||
std::vector<Symbol> attrs;
|
||||
fullattr_t attrs;
|
||||
for (auto & attr : *getValue().attrs)
|
||||
attrs.push_back(attr.name);
|
||||
std::sort(attrs.begin(), attrs.end(), [&](Symbol a, Symbol b) {
|
||||
std::string_view sa = root->state.symbols[a], sb = root->state.symbols[b];
|
||||
return sa < sb;
|
||||
});
|
||||
attrs.p.push_back(root->state.symbols[attr.name]);
|
||||
std::sort(attrs.p.begin(), attrs.p.end());
|
||||
|
||||
if (root->db)
|
||||
cachedValue = {root->db->setAttrs(getKey(), attrs), attrs};
|
||||
|
||||
return attrs;
|
||||
return attrs.p;
|
||||
}
|
||||
|
||||
bool AttrCursor::isDerivation()
|
||||
|
@ -758,7 +736,7 @@ bool AttrCursor::isDerivation()
|
|||
|
||||
StorePath AttrCursor::forceDerivation()
|
||||
{
|
||||
auto aDrvPath = getAttr(root->state.s.drvPath);
|
||||
auto aDrvPath = getAttr("drvPath");
|
||||
auto drvPath = root->state.store->parseStorePath(aDrvPath->getString());
|
||||
if (!root->state.store->isValidPath(drvPath) && !settings.readOnlyMode) {
|
||||
/* The eval cache contains 'drvPath', but the actual path has
|
||||
|
|
|
@ -69,16 +69,17 @@ enum AttrType {
|
|||
};
|
||||
|
||||
struct placeholder_t {};
|
||||
struct fullattr_t { std::vector<std::string> p; };
|
||||
struct missing_t {};
|
||||
struct misc_t {};
|
||||
struct failed_t {};
|
||||
struct int_t { NixInt x; };
|
||||
typedef uint64_t AttrId;
|
||||
typedef std::pair<AttrId, Symbol> AttrKey;
|
||||
typedef std::pair<AttrId, std::string> AttrKey;
|
||||
typedef std::pair<std::string, NixStringContext> string_t;
|
||||
|
||||
typedef std::variant<
|
||||
std::vector<Symbol>,
|
||||
fullattr_t,
|
||||
string_t,
|
||||
placeholder_t,
|
||||
missing_t,
|
||||
|
@ -94,7 +95,7 @@ class AttrCursor : public std::enable_shared_from_this<AttrCursor>
|
|||
friend class EvalCache;
|
||||
|
||||
ref<EvalCache> root;
|
||||
typedef std::optional<std::pair<std::shared_ptr<AttrCursor>, Symbol>> Parent;
|
||||
typedef std::optional<std::pair<std::shared_ptr<AttrCursor>, std::string>> Parent;
|
||||
Parent parent;
|
||||
RootValue _value;
|
||||
std::optional<std::pair<AttrId, AttrValue>> cachedValue;
|
||||
|
@ -111,29 +112,25 @@ public:
|
|||
Value * value = nullptr,
|
||||
std::optional<std::pair<AttrId, AttrValue>> && cachedValue = {});
|
||||
|
||||
std::vector<Symbol> getAttrPath() const;
|
||||
std::vector<std::string> getAttrPath() const;
|
||||
|
||||
std::vector<Symbol> getAttrPath(Symbol name) const;
|
||||
std::vector<std::string> getAttrPath(std::string_view name) const;
|
||||
|
||||
std::string getAttrPathStr() const;
|
||||
|
||||
std::string getAttrPathStr(Symbol name) const;
|
||||
std::string getAttrPathStr(std::string_view name) const;
|
||||
|
||||
Suggestions getSuggestionsForAttr(Symbol name);
|
||||
Suggestions getSuggestionsForAttr(const std::string & name);
|
||||
|
||||
std::shared_ptr<AttrCursor> maybeGetAttr(Symbol name);
|
||||
std::shared_ptr<AttrCursor> maybeGetAttr(const std::string & name);
|
||||
|
||||
std::shared_ptr<AttrCursor> maybeGetAttr(std::string_view name);
|
||||
|
||||
ref<AttrCursor> getAttr(Symbol name);
|
||||
|
||||
ref<AttrCursor> getAttr(std::string_view name);
|
||||
ref<AttrCursor> getAttr(const std::string & name);
|
||||
|
||||
/**
|
||||
* Get an attribute along a chain of attrsets. Note that this does
|
||||
* not auto-call functors or functions.
|
||||
*/
|
||||
OrSuggestions<ref<AttrCursor>> findAlongAttrPath(const std::vector<Symbol> & attrPath);
|
||||
OrSuggestions<ref<AttrCursor>> findAlongAttrPath(const Strings & attrPath);
|
||||
|
||||
std::string getString();
|
||||
|
||||
|
@ -145,7 +142,7 @@ public:
|
|||
|
||||
std::vector<std::string> getListOfStrings();
|
||||
|
||||
std::vector<Symbol> getAttrs();
|
||||
std::vector<std::string> getAttrs();
|
||||
|
||||
bool isDerivation();
|
||||
|
||||
|
|
|
@ -228,7 +228,6 @@ StaticSymbols::StaticSymbols(SymbolTable & symbols)
|
|||
, recurseForDerivations(symbols.create("recurseForDerivations"))
|
||||
, description(symbols.create("description"))
|
||||
, self(symbols.create("self"))
|
||||
, epsilon(symbols.create(""))
|
||||
, startSet(symbols.create("startSet"))
|
||||
, operator_(symbols.create("operator"))
|
||||
, key(symbols.create("key"))
|
||||
|
|
|
@ -210,7 +210,7 @@ struct StaticSymbols
|
|||
ignoreNulls, file, line, column, functor, toString, right, wrong, structuredAttrs,
|
||||
allowedReferences, allowedRequisites, disallowedReferences, disallowedRequisites, maxSize,
|
||||
maxClosureSize, builder, args, contentAddressed, impure, outputHash, outputHashAlgo,
|
||||
outputHashMode, recurseForDerivations, description, self, epsilon, startSet, operator_, key,
|
||||
outputHashMode, recurseForDerivations, description, self, startSet, operator_, key,
|
||||
path, prefix, outputSpecified;
|
||||
|
||||
const Expr::AstSymbols exprSymbols;
|
||||
|
|
|
@ -97,15 +97,6 @@ public:
|
|||
return Symbol(idx + 1);
|
||||
}
|
||||
|
||||
std::vector<SymbolStr> resolve(const std::vector<Symbol> & symbols) const
|
||||
{
|
||||
std::vector<SymbolStr> result;
|
||||
result.reserve(symbols.size());
|
||||
for (auto sym : symbols)
|
||||
result.push_back((*this)[sym]);
|
||||
return result;
|
||||
}
|
||||
|
||||
SymbolStr operator[](Symbol s) const
|
||||
{
|
||||
if (s.id == 0 || s.id > store.size())
|
||||
|
|
|
@ -59,7 +59,7 @@ UnresolvedApp InstallableValue::toApp()
|
|||
auto type = cursor->getAttr("type")->getString();
|
||||
|
||||
std::string expected = !attrPath.empty() &&
|
||||
(state->symbols[attrPath[0]] == "apps" || state->symbols[attrPath[0]] == "defaultApp")
|
||||
(attrPath[0] == "apps" || attrPath[0] == "defaultApp")
|
||||
? "app" : "derivation";
|
||||
if (type != expected)
|
||||
throw Error("attribute '%s' should have type '%s'", cursor->getAttrPathStr(), expected);
|
||||
|
@ -99,11 +99,11 @@ UnresolvedApp InstallableValue::toApp()
|
|||
|
||||
else if (type == "derivation") {
|
||||
auto drvPath = cursor->forceDerivation();
|
||||
auto outPath = cursor->getAttr(state->s.outPath)->getString();
|
||||
auto outputName = cursor->getAttr(state->s.outputName)->getString();
|
||||
auto name = cursor->getAttr(state->s.name)->getString();
|
||||
auto outPath = cursor->getAttr("outPath")->getString();
|
||||
auto outputName = cursor->getAttr("outputName")->getString();
|
||||
auto name = cursor->getAttr("name")->getString();
|
||||
auto aPname = cursor->maybeGetAttr("pname");
|
||||
auto aMeta = cursor->maybeGetAttr(state->s.meta);
|
||||
auto aMeta = cursor->maybeGetAttr("meta");
|
||||
auto aMainProgram = aMeta ? aMeta->maybeGetAttr("mainProgram") : nullptr;
|
||||
auto mainProgram =
|
||||
aMainProgram
|
||||
|
|
134
lix/nix/flake.cc
134
lix/nix/flake.cc
|
@ -1136,8 +1136,8 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
|||
|
||||
std::function<bool(
|
||||
eval_cache::AttrCursor & visitor,
|
||||
const std::vector<Symbol> &attrPath,
|
||||
const Symbol &attr)> hasContent;
|
||||
std::vector<std::string> attrPath,
|
||||
const std::string &attr)> hasContent;
|
||||
|
||||
// For frameworks it's important that structures are as lazy as possible
|
||||
// to prevent infinite recursions, performance issues and errors that
|
||||
|
@ -1147,39 +1147,36 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
|||
// so we omit them.
|
||||
hasContent = [&](
|
||||
eval_cache::AttrCursor & visitor,
|
||||
const std::vector<Symbol> &attrPath,
|
||||
const Symbol &attr) -> bool
|
||||
std::vector<std::string> attrPath,
|
||||
const std::string &attr) -> bool
|
||||
{
|
||||
auto attrPath2(attrPath);
|
||||
attrPath2.push_back(attr);
|
||||
auto attrPathS = state->symbols.resolve(attrPath2);
|
||||
const auto & attrName = state->symbols[attr];
|
||||
attrPath.push_back(attr);
|
||||
|
||||
auto visitor2 = visitor.getAttr(attrName);
|
||||
auto visitor2 = visitor.getAttr(attr);
|
||||
|
||||
try {
|
||||
if ((attrPathS[0] == "apps"
|
||||
|| attrPathS[0] == "checks"
|
||||
|| attrPathS[0] == "devShells"
|
||||
|| attrPathS[0] == "legacyPackages"
|
||||
|| attrPathS[0] == "packages")
|
||||
&& (attrPathS.size() == 1 || attrPathS.size() == 2)) {
|
||||
if ((attrPath[0] == "apps"
|
||||
|| attrPath[0] == "checks"
|
||||
|| attrPath[0] == "devShells"
|
||||
|| attrPath[0] == "legacyPackages"
|
||||
|| attrPath[0] == "packages")
|
||||
&& (attrPath.size() == 1 || attrPath.size() == 2)) {
|
||||
for (const auto &subAttr : visitor2->getAttrs()) {
|
||||
if (hasContent(*visitor2, attrPath2, subAttr)) {
|
||||
if (hasContent(*visitor2, attrPath, subAttr)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((attrPathS.size() == 1)
|
||||
&& (attrPathS[0] == "formatter"
|
||||
|| attrPathS[0] == "nixosConfigurations"
|
||||
|| attrPathS[0] == "nixosModules"
|
||||
|| attrPathS[0] == "overlays"
|
||||
if ((attrPath.size() == 1)
|
||||
&& (attrPath[0] == "formatter"
|
||||
|| attrPath[0] == "nixosConfigurations"
|
||||
|| attrPath[0] == "nixosModules"
|
||||
|| attrPath[0] == "overlays"
|
||||
)) {
|
||||
for (const auto &subAttr : visitor2->getAttrs()) {
|
||||
if (hasContent(*visitor2, attrPath2, subAttr)) {
|
||||
if (hasContent(*visitor2, attrPath, subAttr)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1198,54 +1195,51 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
|||
|
||||
std::function<nlohmann::json(
|
||||
eval_cache::AttrCursor & visitor,
|
||||
const std::vector<Symbol> & attrPath,
|
||||
const std::vector<std::string> & attrPath,
|
||||
const std::string & headerPrefix,
|
||||
const std::string & nextPrefix)> visit;
|
||||
|
||||
visit = [&](
|
||||
eval_cache::AttrCursor & visitor,
|
||||
const std::vector<Symbol> & attrPath,
|
||||
const std::vector<std::string> & attrPath,
|
||||
const std::string & headerPrefix,
|
||||
const std::string & nextPrefix)
|
||||
-> nlohmann::json
|
||||
{
|
||||
auto j = nlohmann::json::object();
|
||||
|
||||
auto attrPathS = state->symbols.resolve(attrPath);
|
||||
|
||||
Activity act(*logger, lvlInfo, actUnknown,
|
||||
fmt("evaluating '%s'", concatStringsSep(".", attrPathS)));
|
||||
fmt("evaluating '%s'", concatStringsSep(".", attrPath)));
|
||||
|
||||
try {
|
||||
auto recurse = [&]()
|
||||
{
|
||||
if (!json)
|
||||
logger->cout("%s", headerPrefix);
|
||||
std::vector<Symbol> attrs;
|
||||
std::vector<std::string> attrs;
|
||||
for (const auto &attr : visitor.getAttrs()) {
|
||||
if (hasContent(visitor, attrPath, attr))
|
||||
attrs.push_back(attr);
|
||||
}
|
||||
|
||||
for (const auto & [i, attr] : enumerate(attrs)) {
|
||||
const auto & attrName = state->symbols[attr];
|
||||
bool last = i + 1 == attrs.size();
|
||||
auto visitor2 = visitor.getAttr(attrName);
|
||||
auto visitor2 = visitor.getAttr(attr);
|
||||
auto attrPath2(attrPath);
|
||||
attrPath2.push_back(attr);
|
||||
auto j2 = visit(*visitor2, attrPath2,
|
||||
fmt(ANSI_GREEN "%s%s" ANSI_NORMAL ANSI_BOLD "%s" ANSI_NORMAL, nextPrefix, last ? treeLast : treeConn, attrName),
|
||||
fmt(ANSI_GREEN "%s%s" ANSI_NORMAL ANSI_BOLD "%s" ANSI_NORMAL, nextPrefix, last ? treeLast : treeConn, attr),
|
||||
nextPrefix + (last ? treeNull : treeLine));
|
||||
if (json) j.emplace(attrName, std::move(j2));
|
||||
if (json) j.emplace(attr, std::move(j2));
|
||||
}
|
||||
};
|
||||
|
||||
auto showDerivation = [&]()
|
||||
{
|
||||
auto name = visitor.getAttr(state->s.name)->getString();
|
||||
auto name = visitor.getAttr("name")->getString();
|
||||
std::optional<std::string> description;
|
||||
if (auto aMeta = visitor.maybeGetAttr(state->s.meta)) {
|
||||
if (auto aDescription = aMeta->maybeGetAttr(state->s.description))
|
||||
if (auto aMeta = visitor.maybeGetAttr("meta")) {
|
||||
if (auto aDescription = aMeta->maybeGetAttr("description"))
|
||||
description = aDescription->getString();
|
||||
}
|
||||
|
||||
|
@ -1256,10 +1250,10 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
|||
j.emplace("description", *description);
|
||||
} else {
|
||||
auto type =
|
||||
attrPath.size() == 2 && attrPathS[0] == "devShell" ? "development environment" :
|
||||
attrPath.size() >= 2 && attrPathS[0] == "devShells" ? "development environment" :
|
||||
attrPath.size() == 3 && attrPathS[0] == "checks" ? "derivation" :
|
||||
attrPath.size() >= 1 && attrPathS[0] == "hydraJobs" ? "derivation" :
|
||||
attrPath.size() == 2 && attrPath[0] == "devShell" ? "development environment" :
|
||||
attrPath.size() >= 2 && attrPath[0] == "devShells" ? "development environment" :
|
||||
attrPath.size() == 3 && attrPath[0] == "checks" ? "derivation" :
|
||||
attrPath.size() >= 1 && attrPath[0] == "hydraJobs" ? "derivation" :
|
||||
"package";
|
||||
|
||||
std::string output = fmt("%s: %s '%s'", headerPrefix, type, name);
|
||||
|
@ -1300,34 +1294,34 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
|||
|
||||
if (attrPath.size() == 0
|
||||
|| (attrPath.size() == 1 && (
|
||||
attrPathS[0] == "defaultPackage"
|
||||
|| attrPathS[0] == "devShell"
|
||||
|| attrPathS[0] == "formatter"
|
||||
|| attrPathS[0] == "nixosConfigurations"
|
||||
|| attrPathS[0] == "nixosModules"
|
||||
|| attrPathS[0] == "defaultApp"
|
||||
|| attrPathS[0] == "templates"
|
||||
|| attrPathS[0] == "overlays"))
|
||||
attrPath[0] == "defaultPackage"
|
||||
|| attrPath[0] == "devShell"
|
||||
|| attrPath[0] == "formatter"
|
||||
|| attrPath[0] == "nixosConfigurations"
|
||||
|| attrPath[0] == "nixosModules"
|
||||
|| attrPath[0] == "defaultApp"
|
||||
|| attrPath[0] == "templates"
|
||||
|| attrPath[0] == "overlays"))
|
||||
|| ((attrPath.size() == 1 || attrPath.size() == 2)
|
||||
&& (attrPathS[0] == "checks"
|
||||
|| attrPathS[0] == "packages"
|
||||
|| attrPathS[0] == "devShells"
|
||||
|| attrPathS[0] == "apps"))
|
||||
&& (attrPath[0] == "checks"
|
||||
|| attrPath[0] == "packages"
|
||||
|| attrPath[0] == "devShells"
|
||||
|| attrPath[0] == "apps"))
|
||||
)
|
||||
{
|
||||
recurse();
|
||||
}
|
||||
|
||||
else if (
|
||||
(attrPath.size() == 2 && (attrPathS[0] == "defaultPackage" || attrPathS[0] == "devShell" || attrPathS[0] == "formatter"))
|
||||
|| (attrPath.size() == 3 && (attrPathS[0] == "checks" || attrPathS[0] == "packages" || attrPathS[0] == "devShells"))
|
||||
(attrPath.size() == 2 && (attrPath[0] == "defaultPackage" || attrPath[0] == "devShell" || attrPath[0] == "formatter"))
|
||||
|| (attrPath.size() == 3 && (attrPath[0] == "checks" || attrPath[0] == "packages" || attrPath[0] == "devShells"))
|
||||
)
|
||||
{
|
||||
if (!showAllSystems && std::string(attrPathS[1]) != localSystem) {
|
||||
if (!showAllSystems && attrPath[1] != localSystem) {
|
||||
if (!json)
|
||||
logger->cout(fmt("%s " ANSI_WARNING "omitted" ANSI_NORMAL " (use '--all-systems' to show)", headerPrefix));
|
||||
else {
|
||||
logger->warn(fmt("%s omitted (use '--all-systems' to show)", concatStringsSep(".", attrPathS)));
|
||||
logger->warn(fmt("%s omitted (use '--all-systems' to show)", concatStringsSep(".", attrPath)));
|
||||
}
|
||||
} else {
|
||||
if (visitor.isDerivation())
|
||||
|
@ -1337,27 +1331,27 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
|||
}
|
||||
}
|
||||
|
||||
else if (attrPath.size() > 0 && attrPathS[0] == "hydraJobs") {
|
||||
else if (attrPath.size() > 0 && attrPath[0] == "hydraJobs") {
|
||||
if (visitor.isDerivation())
|
||||
showDerivation();
|
||||
else
|
||||
recurse();
|
||||
}
|
||||
|
||||
else if (attrPath.size() > 0 && attrPathS[0] == "legacyPackages") {
|
||||
else if (attrPath.size() > 0 && attrPath[0] == "legacyPackages") {
|
||||
if (attrPath.size() == 1)
|
||||
recurse();
|
||||
else if (!showLegacy){
|
||||
if (!json)
|
||||
logger->cout(fmt("%s " ANSI_WARNING "omitted" ANSI_NORMAL " (use '--legacy' to show)", headerPrefix));
|
||||
else {
|
||||
logger->warn(fmt("%s omitted (use '--legacy' to show)", concatStringsSep(".", attrPathS)));
|
||||
logger->warn(fmt("%s omitted (use '--legacy' to show)", concatStringsSep(".", attrPath)));
|
||||
}
|
||||
} else if (!showAllSystems && std::string(attrPathS[1]) != localSystem) {
|
||||
} else if (!showAllSystems && attrPath[1] != localSystem) {
|
||||
if (!json)
|
||||
logger->cout(fmt("%s " ANSI_WARNING "omitted" ANSI_NORMAL " (use '--all-systems' to show)", headerPrefix));
|
||||
else {
|
||||
logger->warn(fmt("%s omitted (use '--all-systems' to show)", concatStringsSep(".", attrPathS)));
|
||||
logger->warn(fmt("%s omitted (use '--all-systems' to show)", concatStringsSep(".", attrPath)));
|
||||
}
|
||||
} else {
|
||||
if (visitor.isDerivation())
|
||||
|
@ -1369,8 +1363,8 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
|||
}
|
||||
|
||||
else if (
|
||||
(attrPath.size() == 2 && attrPathS[0] == "defaultApp") ||
|
||||
(attrPath.size() == 3 && attrPathS[0] == "apps"))
|
||||
(attrPath.size() == 2 && attrPath[0] == "defaultApp") ||
|
||||
(attrPath.size() == 3 && attrPath[0] == "apps"))
|
||||
{
|
||||
auto aType = visitor.maybeGetAttr("type");
|
||||
if (!aType || aType->getString() != "app")
|
||||
|
@ -1383,8 +1377,8 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
|||
}
|
||||
|
||||
else if (
|
||||
(attrPath.size() == 1 && attrPathS[0] == "defaultTemplate") ||
|
||||
(attrPath.size() == 2 && attrPathS[0] == "templates"))
|
||||
(attrPath.size() == 1 && attrPath[0] == "defaultTemplate") ||
|
||||
(attrPath.size() == 2 && attrPath[0] == "templates"))
|
||||
{
|
||||
auto description = visitor.getAttr("description")->getString();
|
||||
if (json) {
|
||||
|
@ -1397,11 +1391,11 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
|||
|
||||
else {
|
||||
auto [type, description] =
|
||||
(attrPath.size() == 1 && attrPathS[0] == "overlay")
|
||||
|| (attrPath.size() == 2 && attrPathS[0] == "overlays") ? std::make_pair("nixpkgs-overlay", "Nixpkgs overlay") :
|
||||
attrPath.size() == 2 && attrPathS[0] == "nixosConfigurations" ? std::make_pair("nixos-configuration", "NixOS configuration") :
|
||||
(attrPath.size() == 1 && attrPathS[0] == "nixosModule")
|
||||
|| (attrPath.size() == 2 && attrPathS[0] == "nixosModules") ? std::make_pair("nixos-module", "NixOS module") :
|
||||
(attrPath.size() == 1 && attrPath[0] == "overlay")
|
||||
|| (attrPath.size() == 2 && attrPath[0] == "overlays") ? std::make_pair("nixpkgs-overlay", "Nixpkgs overlay") :
|
||||
attrPath.size() == 2 && attrPath[0] == "nixosConfigurations" ? std::make_pair("nixos-configuration", "NixOS configuration") :
|
||||
(attrPath.size() == 1 && attrPath[0] == "nixosModule")
|
||||
|| (attrPath.size() == 2 && attrPath[0] == "nixosModules") ? std::make_pair("nixos-module", "NixOS module") :
|
||||
std::make_pair("unknown", "unknown");
|
||||
if (json) {
|
||||
j.emplace("type", type);
|
||||
|
@ -1410,7 +1404,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
|||
}
|
||||
}
|
||||
} catch (EvalError & e) {
|
||||
if (!(attrPath.size() > 0 && attrPathS[0] == "legacyPackages"))
|
||||
if (!(attrPath.size() > 0 && attrPath[0] == "legacyPackages"))
|
||||
throw;
|
||||
}
|
||||
|
||||
|
|
|
@ -91,33 +91,31 @@ struct CmdSearch : InstallableCommand, MixJSON
|
|||
|
||||
uint64_t results = 0;
|
||||
|
||||
std::function<void(eval_cache::AttrCursor & cursor, const std::vector<Symbol> & attrPath, bool initialRecurse)> visit;
|
||||
std::function<void(eval_cache::AttrCursor & cursor, const std::vector<std::string> & attrPath, bool initialRecurse)> visit;
|
||||
|
||||
visit = [&](eval_cache::AttrCursor & cursor, const std::vector<Symbol> & attrPath, bool initialRecurse)
|
||||
visit = [&](eval_cache::AttrCursor & cursor, const std::vector<std::string> & attrPath, bool initialRecurse)
|
||||
{
|
||||
auto attrPathS = state->symbols.resolve(attrPath);
|
||||
|
||||
Activity act(*logger, lvlInfo, actUnknown,
|
||||
fmt("evaluating '%s'", concatStringsSep(".", attrPathS)));
|
||||
fmt("evaluating '%s'", concatStringsSep(".", attrPath)));
|
||||
try {
|
||||
auto recurse = [&]()
|
||||
{
|
||||
for (const auto & attr : cursor.getAttrs()) {
|
||||
auto cursor2 = cursor.getAttr(state->symbols[attr]);
|
||||
auto cursor2 = cursor.getAttr(attr);
|
||||
auto attrPath2(attrPath);
|
||||
attrPath2.push_back(attr);
|
||||
attrPath2.emplace_back(attr);
|
||||
visit(*cursor2, attrPath2, false);
|
||||
}
|
||||
};
|
||||
|
||||
if (cursor.isDerivation()) {
|
||||
DrvName name(cursor.getAttr(state->s.name)->getString());
|
||||
DrvName name(cursor.getAttr("name")->getString());
|
||||
|
||||
auto aMeta = cursor.maybeGetAttr(state->s.meta);
|
||||
auto aDescription = aMeta ? aMeta->maybeGetAttr(state->s.description) : nullptr;
|
||||
auto aMeta = cursor.maybeGetAttr("meta");
|
||||
auto aDescription = aMeta ? aMeta->maybeGetAttr("description") : nullptr;
|
||||
auto description = aDescription ? aDescription->getString() : "";
|
||||
std::replace(description.begin(), description.end(), '\n', ' ');
|
||||
auto attrPath2 = concatStringsSep(".", attrPathS);
|
||||
auto attrPath2 = concatStringsSep(".", attrPath);
|
||||
|
||||
std::vector<std::smatch> attrPathMatches;
|
||||
std::vector<std::smatch> descriptionMatches;
|
||||
|
@ -175,21 +173,21 @@ struct CmdSearch : InstallableCommand, MixJSON
|
|||
|
||||
else if (
|
||||
attrPath.size() == 0
|
||||
|| (attrPathS[0] == "legacyPackages" && attrPath.size() <= 2)
|
||||
|| (attrPathS[0] == "packages" && attrPath.size() <= 2))
|
||||
|| (attrPath[0] == "legacyPackages" && attrPath.size() <= 2)
|
||||
|| (attrPath[0] == "packages" && attrPath.size() <= 2))
|
||||
recurse();
|
||||
|
||||
else if (initialRecurse)
|
||||
recurse();
|
||||
|
||||
else if (attrPathS[0] == "legacyPackages" && attrPath.size() > 2) {
|
||||
auto attr = cursor.maybeGetAttr(state->s.recurseForDerivations);
|
||||
else if (attrPath[0] == "legacyPackages" && attrPath.size() > 2) {
|
||||
auto attr = cursor.maybeGetAttr("recurseForDerivations");
|
||||
if (attr && attr->getBool())
|
||||
recurse();
|
||||
}
|
||||
|
||||
} catch (EvalError & e) {
|
||||
if (!(attrPath.size() > 0 && attrPathS[0] == "legacyPackages"))
|
||||
if (!(attrPath.size() > 0 && attrPath[0] == "legacyPackages"))
|
||||
throw;
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue