about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/libexpr/get-drvs.cc44
-rw-r--r--src/libexpr/get-drvs.hh39
-rw-r--r--src/libutil/args.cc1
-rw-r--r--src/libutil/args.hh4
-rw-r--r--src/nix-env/nix-env.cc90
-rw-r--r--src/nix-env/user-env.cc7
-rw-r--r--src/nix/command.hh34
-rw-r--r--src/nix/installables.cc16
-rw-r--r--src/nix/search.cc130
9 files changed, 263 insertions, 102 deletions
diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc
index 4200e8fd6757..b7e16de7fa4e 100644
--- a/src/libexpr/get-drvs.cc
+++ b/src/libexpr/get-drvs.cc
@@ -9,7 +9,34 @@
 namespace nix {
 
 
-string DrvInfo::queryDrvPath()
+DrvInfo::DrvInfo(EvalState & state, const string & attrPath, Bindings * attrs)
+    : state(&state), attrs(attrs), attrPath(attrPath)
+{
+}
+
+
+string DrvInfo::queryName() const
+{
+    if (name == "" && attrs) {
+        auto i = attrs->find(state->sName);
+        if (i == attrs->end()) throw TypeError("derivation name missing");
+        name = state->forceStringNoCtx(*i->value);
+    }
+    return name;
+}
+
+
+string DrvInfo::querySystem() const
+{
+    if (system == "" && attrs) {
+        auto i = attrs->find(state->sSystem);
+        system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, *i->pos);
+    }
+    return system;
+}
+
+
+string DrvInfo::queryDrvPath() const
 {
     if (drvPath == "" && attrs) {
         Bindings::iterator i = attrs->find(state->sDrvPath);
@@ -20,7 +47,7 @@ string DrvInfo::queryDrvPath()
 }
 
 
-string DrvInfo::queryOutPath()
+string DrvInfo::queryOutPath() const
 {
     if (outPath == "" && attrs) {
         Bindings::iterator i = attrs->find(state->sOutPath);
@@ -76,7 +103,7 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall)
 }
 
 
-string DrvInfo::queryOutputName()
+string DrvInfo::queryOutputName() const
 {
     if (outputName == "" && attrs) {
         Bindings::iterator i = attrs->find(state->sOutputName);
@@ -225,17 +252,12 @@ static bool getDerivation(EvalState & state, Value & v,
         if (done.find(v.attrs) != done.end()) return false;
         done.insert(v.attrs);
 
-        Bindings::iterator i = v.attrs->find(state.sName);
-        /* !!! We really would like to have a decent back trace here. */
-        if (i == v.attrs->end()) throw TypeError("derivation name missing");
+        DrvInfo drv(state, attrPath, v.attrs);
 
-        Bindings::iterator i2 = v.attrs->find(state.sSystem);
-
-        DrvInfo drv(state, state.forceStringNoCtx(*i->value), attrPath,
-            i2 == v.attrs->end() ? "unknown" : state.forceStringNoCtx(*i2->value, *i2->pos),
-            v.attrs);
+        drv.queryName();
 
         drvs.push_back(drv);
+
         return false;
 
     } catch (AssertionError & e) {
diff --git a/src/libexpr/get-drvs.hh b/src/libexpr/get-drvs.hh
index 37fcbe829d3c..82fb8a3ac6a8 100644
--- a/src/libexpr/get-drvs.hh
+++ b/src/libexpr/get-drvs.hh
@@ -17,31 +17,32 @@ public:
 private:
     EvalState * state;
 
-    string drvPath;
-    string outPath;
-    string outputName;
+    mutable string name;
+    mutable string system;
+    mutable string drvPath;
+    mutable string outPath;
+    mutable string outputName;
     Outputs outputs;
 
-    bool failed; // set if we get an AssertionError
+    bool failed = false; // set if we get an AssertionError
 
-    Bindings * attrs, * meta;
+    Bindings * attrs = nullptr, * meta = nullptr;
 
     Bindings * getMeta();
 
     bool checkMeta(Value & v);
 
 public:
-    string name;
     string attrPath; /* path towards the derivation */
-    string system;
 
-    DrvInfo(EvalState & state) : state(&state), failed(false), attrs(0), meta(0) { };
-    DrvInfo(EvalState & state, const string & name, const string & attrPath, const string & system, Bindings * attrs)
-        : state(&state), failed(false), attrs(attrs), meta(0), name(name), attrPath(attrPath), system(system) { };
+    DrvInfo(EvalState & state) : state(&state) { };
+    DrvInfo(EvalState & state, const string & attrPath, Bindings * attrs);
 
-    string queryDrvPath();
-    string queryOutPath();
-    string queryOutputName();
+    string queryName() const;
+    string querySystem() const;
+    string queryDrvPath() const;
+    string queryOutPath() const;
+    string queryOutputName() const;
     /** Return the list of outputs. The "outputs to install" are determined by `mesa.outputsToInstall`. */
     Outputs queryOutputs(bool onlyOutputsToInstall = false);
 
@@ -58,15 +59,9 @@ public:
     MetaValue queryMetaInfo(EvalState & state, const string & name) const;
     */
 
-    void setDrvPath(const string & s)
-    {
-        drvPath = s;
-    }
-
-    void setOutPath(const string & s)
-    {
-        outPath = s;
-    }
+    void setName(const string & s) { name = s; }
+    void setDrvPath(const string & s) { drvPath = s; }
+    void setOutPath(const string & s) { outPath = s; }
 
     void setFailed() { failed = true; };
     bool hasFailed() { return failed; };
diff --git a/src/libutil/args.cc b/src/libutil/args.cc
index 0eed4945418d..19a45d7e9b37 100644
--- a/src/libutil/args.cc
+++ b/src/libutil/args.cc
@@ -66,6 +66,7 @@ void Args::printHelp(const string & programName, std::ostream & out)
         std::cout << renderLabels({exp.label});
         // FIXME: handle arity > 1
         if (exp.arity == 0) std::cout << "...";
+        if (exp.optional) std::cout << "?";
     }
     std::cout << "\n";
 
diff --git a/src/libutil/args.hh b/src/libutil/args.hh
index ef8a7953e520..37e780dd1748 100644
--- a/src/libutil/args.hh
+++ b/src/libutil/args.hh
@@ -164,9 +164,9 @@ public:
     }
 
     /* Expect a string argument. */
-    void expectArg(const std::string & label, string * dest)
+    void expectArg(const std::string & label, string * dest, bool optional = false)
     {
-        expectedArgs.push_back(ExpectedArg{label, 1, false, [=](Strings ss) {
+        expectedArgs.push_back(ExpectedArg{label, 1, optional, [=](Strings ss) {
             *dest = ss.front();
         }});
     }
diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc
index 10100d6a6019..8620cd25574e 100644
--- a/src/nix-env/nix-env.cc
+++ b/src/nix-env/nix-env.cc
@@ -186,7 +186,7 @@ static void loadDerivations(EvalState & state, Path nixExprPath,
        system. */
     for (DrvInfos::iterator i = elems.begin(), j; i != elems.end(); i = j) {
         j = i; j++;
-        if (systemFilter != "*" && i->system != systemFilter)
+        if (systemFilter != "*" && i->querySystem() != systemFilter)
             elems.erase(i);
     }
 }
@@ -247,7 +247,7 @@ static DrvInfos filterBySelector(EvalState & state, const DrvInfos & allElems,
         for (DrvInfos::const_iterator j = allElems.begin();
              j != allElems.end(); ++j, ++n)
         {
-            DrvName drvName(j->name);
+            DrvName drvName(j->queryName());
             if (i.matches(drvName)) {
                 i.hits++;
                 matches.push_back(std::pair<DrvInfo, unsigned int>(*j, n));
@@ -269,36 +269,36 @@ static DrvInfos filterBySelector(EvalState & state, const DrvInfos & allElems,
             StringSet multiple;
 
             for (auto & j : matches) {
-                DrvName drvName(j.first.name);
+                DrvName drvName(j.first.queryName());
                 int d = 1;
 
                 Newest::iterator k = newest.find(drvName.name);
 
                 if (k != newest.end()) {
-                    d = j.first.system == k->second.first.system ? 0 :
-                        j.first.system == settings.thisSystem ? 1 :
-                        k->second.first.system == settings.thisSystem ? -1 : 0;
+                    d = j.first.querySystem() == k->second.first.querySystem() ? 0 :
+                        j.first.querySystem() == settings.thisSystem ? 1 :
+                        k->second.first.querySystem() == settings.thisSystem ? -1 : 0;
                     if (d == 0)
                         d = comparePriorities(state, j.first, k->second.first);
                     if (d == 0)
-                        d = compareVersions(drvName.version, DrvName(k->second.first.name).version);
+                        d = compareVersions(drvName.version, DrvName(k->second.first.queryName()).version);
                 }
 
                 if (d > 0) {
                     newest.erase(drvName.name);
                     newest.insert(Newest::value_type(drvName.name, j));
-                    multiple.erase(j.first.name);
+                    multiple.erase(j.first.queryName());
                 } else if (d == 0) {
-                    multiple.insert(j.first.name);
+                    multiple.insert(j.first.queryName());
                 }
             }
 
             matches.clear();
             for (auto & j : newest) {
-                if (multiple.find(j.second.first.name) != multiple.end())
+                if (multiple.find(j.second.first.queryName()) != multiple.end())
                     printInfo(
-                        format("warning: there are multiple derivations named ‘%1%’; using the first one")
-                        % j.second.first.name);
+                        "warning: there are multiple derivations named ‘%1%’; using the first one",
+                        j.second.first.queryName());
                 matches.push_back(j.second);
             }
         }
@@ -386,7 +386,8 @@ static void queryInstSources(EvalState & state,
                 if (dash != string::npos)
                     name = string(name, dash + 1);
 
-                DrvInfo elem(state, name, "", "", 0);
+                DrvInfo elem(state, "", nullptr);
+                elem.setName(name);
 
                 if (isDerivation(path)) {
                     elem.setDrvPath(path);
@@ -468,8 +469,8 @@ static void installDerivations(Globals & globals,
            path is not the one we want (e.g., `java-front' versus
            `java-front-0.9pre15899'). */
         if (globals.forceName != "")
-            i.name = globals.forceName;
-        newNames.insert(DrvName(i.name).name);
+            i.setName(globals.forceName);
+        newNames.insert(DrvName(i.queryName()).name);
     }
 
 
@@ -484,17 +485,17 @@ static void installDerivations(Globals & globals,
             DrvInfos installedElems = queryInstalled(*globals.state, profile);
 
             for (auto & i : installedElems) {
-                DrvName drvName(i.name);
+                DrvName drvName(i.queryName());
                 if (!globals.preserveInstalled &&
                     newNames.find(drvName.name) != newNames.end() &&
                     !keep(i))
-                    printInfo(format("replacing old ‘%1%’") % i.name);
+                    printInfo("replacing old ‘%s’", i.queryName());
                 else
                     allElems.push_back(i);
             }
 
             for (auto & i : newElems)
-                printInfo(format("installing ‘%1%’") % i.name);
+                printInfo("installing ‘%s’", i.queryName());
         }
 
         printMissing(*globals.state, newElems);
@@ -548,7 +549,7 @@ static void upgradeDerivations(Globals & globals,
         /* Go through all installed derivations. */
         DrvInfos newElems;
         for (auto & i : installedElems) {
-            DrvName drvName(i.name);
+            DrvName drvName(i.queryName());
 
             try {
 
@@ -569,7 +570,7 @@ static void upgradeDerivations(Globals & globals,
                 for (auto j = availElems.begin(); j != availElems.end(); ++j) {
                     if (comparePriorities(*globals.state, i, *j) > 0)
                         continue;
-                    DrvName newName(j->name);
+                    DrvName newName(j->queryName());
                     if (newName.name == drvName.name) {
                         int d = compareVersions(drvName.version, newName.version);
                         if ((upgradeType == utLt && d < 0) ||
@@ -596,14 +597,13 @@ static void upgradeDerivations(Globals & globals,
                 {
                     const char * action = compareVersions(drvName.version, bestVersion) <= 0
                         ? "upgrading" : "downgrading";
-                    printInfo(
-                        format("%1% ‘%2%’ to ‘%3%’")
-                        % action % i.name % bestElem->name);
+                    printInfo("%1% ‘%2%’ to ‘%3%’",
+                        action, i.queryName(), bestElem->queryName());
                     newElems.push_back(*bestElem);
                 } else newElems.push_back(i);
 
             } catch (Error & e) {
-                e.addPrefix(format("while trying to find an upgrade for ‘%1%’:\n") % i.name);
+                e.addPrefix(fmt("while trying to find an upgrade for ‘%s’:\n", i.queryName()));
                 throw;
             }
         }
@@ -663,10 +663,10 @@ static void opSetFlag(Globals & globals, Strings opFlags, Strings opArgs)
 
         /* Update all matching derivations. */
         for (auto & i : installedElems) {
-            DrvName drvName(i.name);
+            DrvName drvName(i.queryName());
             for (auto & j : selectors)
                 if (j.matches(drvName)) {
-                    printInfo(format("setting flag on ‘%1%’") % i.name);
+                    printInfo("setting flag on ‘%1%’", i.queryName());
                     j.hits++;
                     setMetaFlag(*globals.state, i, flagName, flagValue);
                     break;
@@ -702,7 +702,7 @@ static void opSet(Globals & globals, Strings opFlags, Strings opArgs)
     DrvInfo & drv(elems.front());
 
     if (globals.forceName != "")
-        drv.name = globals.forceName;
+        drv.setName(globals.forceName);
 
     if (drv.queryDrvPath() != "") {
         PathSet paths = {drv.queryDrvPath()};
@@ -732,7 +732,7 @@ static void uninstallDerivations(Globals & globals, Strings & selectors,
         DrvInfos newElems;
 
         for (auto & i : installedElems) {
-            DrvName drvName(i.name);
+            DrvName drvName(i.queryName());
             bool found = false;
             for (auto & j : selectors)
                 /* !!! the repeated calls to followLinksToStorePath()
@@ -740,7 +740,7 @@ static void uninstallDerivations(Globals & globals, Strings & selectors,
                 if ((isPath(j) && i.queryOutPath() == globals.state->store->followLinksToStorePath(j))
                     || DrvName(j).matches(drvName))
                 {
-                    printInfo(format("uninstalling ‘%1%’") % i.name);
+                    printInfo("uninstalling ‘%s’", i.queryName());
                     found = true;
                     break;
                 }
@@ -771,9 +771,11 @@ static bool cmpChars(char a, char b)
 
 static bool cmpElemByName(const DrvInfo & a, const DrvInfo & b)
 {
+    auto a_name = a.queryName();
+    auto b_name = b.queryName();
     return lexicographical_compare(
-        a.name.begin(), a.name.end(),
-        b.name.begin(), b.name.end(), cmpChars);
+        a_name.begin(), a_name.end(),
+        b_name.begin(), b_name.end(), cmpChars);
 }
 
 
@@ -822,13 +824,13 @@ typedef enum { cvLess, cvEqual, cvGreater, cvUnavail } VersionDiff;
 static VersionDiff compareVersionAgainstSet(
     const DrvInfo & elem, const DrvInfos & elems, string & version)
 {
-    DrvName name(elem.name);
+    DrvName name(elem.queryName());
 
     VersionDiff diff = cvUnavail;
     version = "?";
 
     for (auto & i : elems) {
-        DrvName name2(i.name);
+        DrvName name2(i.queryName());
         if (name.name == name2.name) {
             int d = compareVersions(name.version, name2.version);
             if (d < 0) {
@@ -857,8 +859,8 @@ static void queryJSON(Globals & globals, vector<DrvInfo> & elems)
     for (auto & i : elems) {
         JSONObject pkgObj = topObj.object(i.attrPath);
 
-        pkgObj.attr("name", i.name);
-        pkgObj.attr("system", i.system);
+        pkgObj.attr("name", i.queryName());
+        pkgObj.attr("system", i.querySystem());
 
         JSONObject metaObj = pkgObj.object("meta");
         StringSet metaNames = i.queryMetaNames();
@@ -866,7 +868,7 @@ static void queryJSON(Globals & globals, vector<DrvInfo> & elems)
             auto placeholder = metaObj.placeholder(j);
             Value * v = i.queryMeta(j);
             if (!v) {
-                printError(format("derivation ‘%1%’ has invalid meta attribute ‘%2%’") % i.name % j);
+                printError("derivation ‘%s’ has invalid meta attribute ‘%s’", i.queryName(), j);
                 placeholder.write(nullptr);
             } else {
                 PathSet context;
@@ -963,7 +965,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
             try {
                 paths.insert(i.queryOutPath());
             } catch (AssertionError & e) {
-                printMsg(lvlTalkative, format("skipping derivation named ‘%1%’ which gives an assertion failure") % i.name);
+                printMsg(lvlTalkative, "skipping derivation named ‘%s’ which gives an assertion failure", i.queryName());
                 i.setFailed();
             }
         validPaths = globals.state->store->queryValidPaths(paths);
@@ -1024,9 +1026,9 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
                 columns.push_back(i.attrPath);
 
             if (xmlOutput)
-                attrs["name"] = i.name;
+                attrs["name"] = i.queryName();
             else if (printName)
-                columns.push_back(i.name);
+                columns.push_back(i.queryName());
 
             if (compareVersions) {
                 /* Compare this element against the versions of the
@@ -1059,10 +1061,10 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
             }
 
             if (xmlOutput) {
-                if (i.system != "") attrs["system"] = i.system;
+                if (i.querySystem() != "") attrs["system"] = i.querySystem();
             }
             else if (printSystem)
-                columns.push_back(i.system);
+                columns.push_back(i.querySystem());
 
             if (printDrvPath) {
                 string drvPath = i.queryDrvPath();
@@ -1110,7 +1112,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
                             attrs2["name"] = j;
                             Value * v = i.queryMeta(j);
                             if (!v)
-                                printError(format("derivation ‘%1%’ has invalid meta attribute ‘%2%’") % i.name % j);
+                                printError("derivation ‘%s’ has invalid meta attribute ‘%s’", i.queryName(), j);
                             else {
                                 if (v->type == tString) {
                                     attrs2["type"] = "string";
@@ -1161,9 +1163,9 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
             cout.flush();
 
         } catch (AssertionError & e) {
-            printMsg(lvlTalkative, format("skipping derivation named ‘%1%’ which gives an assertion failure") % i.name);
+            printMsg(lvlTalkative, "skipping derivation named ‘%1%’ which gives an assertion failure", i.queryName());
         } catch (Error & e) {
-            e.addPrefix(format("while querying the derivation named ‘%1%’:\n") % i.name);
+            e.addPrefix(fmt("while querying the derivation named ‘%1%’:\n", i.queryName()));
             throw;
         }
     }
diff --git a/src/nix-env/user-env.cc b/src/nix-env/user-env.cc
index e9997fae57ba..df5105f12c2a 100644
--- a/src/nix-env/user-env.cc
+++ b/src/nix-env/user-env.cc
@@ -56,9 +56,10 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
         state.mkAttrs(v, 16);
 
         mkString(*state.allocAttr(v, state.sType), "derivation");
-        mkString(*state.allocAttr(v, state.sName), i.name);
-        if (!i.system.empty())
-            mkString(*state.allocAttr(v, state.sSystem), i.system);
+        mkString(*state.allocAttr(v, state.sName), i.queryName());
+        auto system = i.querySystem();
+        if (!system.empty())
+            mkString(*state.allocAttr(v, state.sSystem), system);
         mkString(*state.allocAttr(v, state.sOutPath), i.queryOutPath());
         if (drvPath != "")
             mkString(*state.allocAttr(v, state.sDrvPath), i.queryDrvPath());
diff --git a/src/nix/command.hh b/src/nix/command.hh
index ae7709b5dc83..536802653819 100644
--- a/src/nix/command.hh
+++ b/src/nix/command.hh
@@ -62,17 +62,13 @@ struct Installable
     }
 };
 
-/* A command that operates on a list of "installables", which can be
-   store paths, attribute paths, Nix expressions, etc. */
-struct InstallablesCommand : virtual Args, StoreCommand
+struct SourceExprCommand : virtual Args, StoreCommand
 {
-    std::vector<std::shared_ptr<Installable>> installables;
     Path file;
 
-    InstallablesCommand()
+    SourceExprCommand()
     {
         mkFlag('f', "file", "file", "evaluate FILE rather than the default", &file);
-        expectArgs("installables", &_installables);
     }
 
     /* Return a value representing the Nix expression from which we
@@ -81,14 +77,32 @@ struct InstallablesCommand : virtual Args, StoreCommand
        = import ...; bla = import ...; }’. */
     Value * getSourceExpr(EvalState & state);
 
+    ref<EvalState> getEvalState();
+
+private:
+
+    std::shared_ptr<EvalState> evalState;
+
+    Value * vSourceExpr = 0;
+};
+
+/* A command that operates on a list of "installables", which can be
+   store paths, attribute paths, Nix expressions, etc. */
+struct InstallablesCommand : virtual Args, SourceExprCommand
+{
+    std::vector<std::shared_ptr<Installable>> installables;
+
+    InstallablesCommand()
+    {
+        expectArgs("installables", &_installables);
+    }
+
     std::vector<std::shared_ptr<Installable>> parseInstallables(ref<Store> store, Strings ss);
 
     enum ToStorePathsMode { Build, NoBuild, DryRun };
 
     PathSet toStorePaths(ref<Store> store, ToStorePathsMode mode);
 
-    ref<EvalState> getEvalState();
-
     void prepare() override;
 
     virtual bool useDefaultInstallables() { return true; }
@@ -96,10 +110,6 @@ struct InstallablesCommand : virtual Args, StoreCommand
 private:
 
     Strings _installables;
-
-    std::shared_ptr<EvalState> evalState;
-
-    Value * vSourceExpr = 0;
 };
 
 /* A command that operates on zero or more store paths. */
diff --git a/src/nix/installables.cc b/src/nix/installables.cc
index 7fad8fe415c6..4da736f4d5c3 100644
--- a/src/nix/installables.cc
+++ b/src/nix/installables.cc
@@ -12,7 +12,7 @@
 
 namespace nix {
 
-Value * InstallablesCommand::getSourceExpr(EvalState & state)
+Value * SourceExprCommand::getSourceExpr(EvalState & state)
 {
     if (vSourceExpr) return vSourceExpr;
 
@@ -59,6 +59,13 @@ Value * InstallablesCommand::getSourceExpr(EvalState & state)
     return vSourceExpr;
 }
 
+ref<EvalState> SourceExprCommand::getEvalState()
+{
+    if (!evalState)
+        evalState = std::make_shared<EvalState>(Strings{}, getStore());
+    return ref<EvalState>(evalState);
+}
+
 struct InstallableStoreDrv : Installable
 {
     Path storePath;
@@ -237,13 +244,6 @@ PathSet InstallablesCommand::toStorePaths(ref<Store> store, ToStorePathsMode mod
     return outPaths;
 }
 
-ref<EvalState> InstallablesCommand::getEvalState()
-{
-    if (!evalState)
-        evalState = std::make_shared<EvalState>(Strings{}, getStore());
-    return ref<EvalState>(evalState);
-}
-
 void InstallablesCommand::prepare()
 {
     installables = parseInstallables(getStore(), _installables);
diff --git a/src/nix/search.cc b/src/nix/search.cc
new file mode 100644
index 000000000000..813f6d0a6226
--- /dev/null
+++ b/src/nix/search.cc
@@ -0,0 +1,130 @@
+#include "command.hh"
+#include "globals.hh"
+#include "eval.hh"
+#include "eval-inline.hh"
+#include "names.hh"
+#include "get-drvs.hh"
+
+#include <regex>
+
+using namespace nix;
+
+std::string hilite(const std::string & s, const std::smatch & m)
+{
+    return
+        m.empty()
+        ? s
+        : std::string(m.prefix())
+          + ANSI_RED + std::string(m.str()) + ANSI_NORMAL
+          + std::string(m.suffix());
+}
+
+struct CmdSearch : SourceExprCommand
+{
+    std::string re;
+
+    CmdSearch()
+    {
+        expectArg("regex", &re, true);
+    }
+
+    std::string name() override
+    {
+        return "search";
+    }
+
+    std::string description() override
+    {
+        return "query available packages";
+    }
+
+    void run(ref<Store> store) override
+    {
+        settings.readOnlyMode = true;
+
+        std::regex regex(re, std::regex::extended | std::regex::icase);
+
+        auto state = getEvalState();
+
+        std::function<void(Value *, std::string, bool)> doExpr;
+
+        bool first = true;
+
+        doExpr = [&](Value * v, std::string attrPath, bool toplevel) {
+            debug("at attribute ‘%s’", attrPath);
+
+            try {
+
+                state->forceValue(*v);
+
+                if (v->type == tLambda && toplevel) {
+                    Value * v2 = state->allocValue();
+                    state->autoCallFunction(*state->allocBindings(1), *v, *v2);
+                    v = v2;
+                    state->forceValue(*v);
+                }
+
+                if (state->isDerivation(*v)) {
+
+                    DrvInfo drv(*state, attrPath, v->attrs);
+
+                    DrvName parsed(drv.queryName());
+
+                    std::smatch attrPathMatch;
+                    std::regex_search(attrPath, attrPathMatch, regex);
+
+                    auto name = parsed.name;
+                    std::smatch nameMatch;
+                    std::regex_search(name, nameMatch, regex);
+
+                    std::string description = drv.queryMetaString("description");
+                    std::replace(description.begin(), description.end(), '\n', ' ');
+                    std::smatch descriptionMatch;
+                    std::regex_search(description, descriptionMatch, regex);
+
+                    if (!attrPathMatch.empty()
+                        || !nameMatch.empty()
+                        || !descriptionMatch.empty())
+                    {
+                        if (!first) std::cout << "\n";
+                        first = false;
+
+                        std::cout << fmt(
+                            "Attribute name: %s\n"
+                            "Package name: %s\n"
+                            "Version: %s\n"
+                            "Description: %s\n",
+                            hilite(attrPath, attrPathMatch),
+                            hilite(name, nameMatch),
+                            parsed.version,
+                            hilite(description, descriptionMatch));
+                    }
+                }
+
+                else if (v->type == tAttrs) {
+
+                    if (!toplevel) {
+                        auto attrs = v->attrs;
+                        Bindings::iterator j = attrs->find(state->symbols.create("recurseForDerivations"));
+                        if (j == attrs->end() || !state->forceBool(*j->value, *j->pos)) return;
+                    }
+
+                    Bindings::iterator j = v->attrs->find(state->symbols.create("_toplevel"));
+                    bool toplevel2 = j != v->attrs->end() && state->forceBool(*j->value, *j->pos);
+
+                    for (auto & i : *v->attrs) {
+                        doExpr(i.value,
+                            attrPath == "" ? (std::string) i.name : attrPath + "." + (std::string) i.name,
+                            toplevel2);
+                    }
+                }
+
+            } catch (AssertionError & e) {
+            }
+        };
+
+        doExpr(getSourceExpr(*state), "", true);
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdSearch>());