profile: allow different types of matchers

This commit is contained in:
Bob van der Linden 2024-02-28 22:27:16 +01:00
parent d83008c3a7
commit 741a6bfad5
No known key found for this signature in database

View file

@ -453,55 +453,86 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
} }
}; };
class MixProfileElementMatchers : virtual Args enum MatcherType
{ {
std::vector<std::string> _matchers; Regex,
StorePath,
};
struct Matcher
{
MatcherType type;
std::string title;
std::function<bool(const std::string & name, const ProfileElement & element)> matches;
};
Matcher createRegexMatcher(const std::string & pattern)
{
std::regex reg(pattern, std::regex::extended | std::regex::icase);
return {
.type = MatcherType::Regex,
.title = fmt("Regex '%s'", pattern),
.matches = [reg](const std::string &name, const ProfileElement & element) {
return std::regex_match(element.identifier(), reg);
},
};
}
Matcher createStorePathMatcher(const nix::StorePath & storePath)
{
return {
.type = MatcherType::StorePath,
.title = fmt("Store path '%s'", storePath.to_string()),
.matches = [storePath](const std::string &name, const ProfileElement & element) {
return element.storePaths.count(storePath);
}
};
}
class MixProfileElementMatchers : virtual Args, virtual StoreCommand
{
std::vector<Matcher> _matchers;
public: public:
MixProfileElementMatchers() MixProfileElementMatchers()
{ {
expectArgs("elements", &_matchers); expectArgs(ExpectedArg {
.label = "elements",
.optional = true,
.handler = {[this](std::vector<std::string> args) {
for (auto & arg : args) {
if (auto n = string2Int<size_t>(arg)) {
throw Error("'nix profile' no longer supports indices ('%d')", *n);
} else if (getStore()->isStorePath(arg)) {
_matchers.push_back(createStorePathMatcher(getStore()->parseStorePath(arg)));
} else {
_matchers.push_back(createRegexMatcher(arg));
}
}
}}
});
} }
struct RegexPattern { std::set<std::string> getMatchingElementNames(ProfileManifest & manifest) {
std::string pattern; if (_matchers.empty()) {
std::regex reg; throw UsageError("No packages specified.");
};
typedef std::variant<Path, RegexPattern> Matcher;
std::vector<Matcher> getMatchers(ref<Store> store)
{
std::vector<Matcher> res;
for (auto & s : _matchers) {
if (auto n = string2Int<size_t>(s))
throw Error("'nix profile' no longer supports indices ('%d')", *n);
else if (store->isStorePath(s))
res.push_back(s);
else
res.push_back(RegexPattern{s,std::regex(s, std::regex::extended | std::regex::icase)});
} }
return res; std::set<std::string> result;
} for (auto & matcher : _matchers) {
bool foundMatch = false;
bool matches( for (auto & [name, element] : manifest.elements) {
const Store & store, if (matcher.matches(name, element)) {
const std::string & name, result.insert(name);
const ProfileElement & element, foundMatch = true;
const std::vector<Matcher> & matchers) }
{ }
for (auto & matcher : matchers) { if (!foundMatch) {
if (auto path = std::get_if<Path>(&matcher)) { warn("%s does not match any packages in the profile.", matcher.title);
if (element.storePaths.count(store.parseStorePath(*path))) return true;
} else if (auto regex = std::get_if<RegexPattern>(&matcher)) {
if (std::regex_match(name, regex->reg))
return true;
} }
} }
return result;
return false;
} }
}; };
@ -523,16 +554,19 @@ struct CmdProfileRemove : virtual EvalCommand, MixDefaultProfile, MixProfileElem
{ {
ProfileManifest oldManifest(*getEvalState(), *profile); ProfileManifest oldManifest(*getEvalState(), *profile);
auto matchers = getMatchers(store); ProfileManifest newManifest = oldManifest;
ProfileManifest newManifest; auto matchingElementNames = getMatchingElementNames(oldManifest);
for (auto & [name, element] : oldManifest.elements) { if (matchingElementNames.empty()) {
if (!matches(*store, name, element, matchers)) { warn ("No packages to remove. Use 'nix profile list' to see the current profile.");
newManifest.elements.insert_or_assign(name, std::move(element)); return;
} else { }
notice("removing '%s'", element.identifier());
} for (auto & name : matchingElementNames) {
auto & element = oldManifest.elements[name];
notice("removing '%s'", element.identifier());
newManifest.elements.erase(name);
} }
auto removedCount = oldManifest.elements.size() - newManifest.elements.size(); auto removedCount = oldManifest.elements.size() - newManifest.elements.size();
@ -540,16 +574,6 @@ struct CmdProfileRemove : virtual EvalCommand, MixDefaultProfile, MixProfileElem
removedCount, removedCount,
newManifest.elements.size()); newManifest.elements.size());
if (removedCount == 0) {
for (auto matcher: matchers) {
if (const Path * path = std::get_if<Path>(&matcher)) {
warn("'%s' does not match any paths", *path);
} else if (const RegexPattern * regex = std::get_if<RegexPattern>(&matcher)) {
warn("'%s' does not match any packages", regex->pattern);
}
}
warn ("Use 'nix profile list' to see the current profile.");
}
updateProfile(newManifest.build(store)); updateProfile(newManifest.build(store));
} }
}; };
@ -572,20 +596,20 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
{ {
ProfileManifest manifest(*getEvalState(), *profile); ProfileManifest manifest(*getEvalState(), *profile);
auto matchers = getMatchers(store);
Installables installables; Installables installables;
std::vector<ProfileElement *> elems; std::vector<ProfileElement *> elems;
auto matchedCount = 0;
auto upgradedCount = 0; auto upgradedCount = 0;
for (auto & [name, element] : manifest.elements) { auto matchingElementNames = getMatchingElementNames(manifest);
if (!matches(*store, name, element, matchers)) {
continue;
}
matchedCount++; if (matchingElementNames.empty()) {
warn("No packages to upgrade. Use 'nix profile list' to see the current profile.");
return;
}
for (auto & name : matchingElementNames) {
auto & element = manifest.elements[name];
if (!element.source) { if (!element.source) {
warn( warn(
@ -641,18 +665,8 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
} }
if (upgradedCount == 0) { if (upgradedCount == 0) {
if (matchedCount == 0) { warn("Found some packages but none of them could be upgraded.");
for (auto & matcher : matchers) { return;
if (const Path * path = std::get_if<Path>(&matcher)) {
warn("'%s' does not match any paths", *path);
} else if (const RegexPattern * regex = std::get_if<RegexPattern>(&matcher)) {
warn("'%s' does not match any packages", regex->pattern);
}
}
} else {
warn("Found some packages but none of them could be upgraded.");
}
warn ("Use 'nix profile list' to see the current profile.");
} }
auto builtPaths = builtPathsPerInstallable( auto builtPaths = builtPathsPerInstallable(