From 0c725f9a03119c6b7e55ee607ef084bd6d97681c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magnus=20Gro=C3=9F?=
<21310755+vimpostor@users.noreply.github.com>
Date: Sat, 5 Sep 2026 20:58:37 +0200
Subject: [PATCH] Allow to filter sets by release date (#7239)
This compares the release dates of sets, which enables users to filter
for sets in a certain range, for example to filter for all commanders
with an old card frame, `t:legendary set<8ED` can be used, which will
only include cards appearing before 8th edition.
This acts as a more powerful superset of the "Filter to X most recent
sets" feature.
Fixes #7238
---
cockatrice/resources/help/search.md | 1 +
.../libcockatrice/filters/filter_string.cpp | 68 +++++++++++++------
.../libcockatrice/filters/filter_string.h | 1 +
3 files changed, 51 insertions(+), 19 deletions(-)
diff --git a/cockatrice/resources/help/search.md b/cockatrice/resources/help/search.md
index 0c8bdb450..fd0a12507 100644
--- a/cockatrice/resources/help/search.md
+++ b/cockatrice/resources/help/search.md
@@ -52,6 +52,7 @@ In this list of examples below, each entry has an explanation and can be clicked
Edition:
[set:lea](#set:lea) (Cards that appear in Alpha, which has the set code LEA)
[e:lea OR e:leb](#e:lea OR e:leb) (Cards that appear in Alpha or Beta)
+[e<8ED](#e<8ED) (Cards that appear before 8th edition)
Negate:
[c:wu -c:m](#c:wu -c:m) (Any card that is white or blue, but not multicolored)
diff --git a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp
index 25e8e97db..aaf391c03 100644
--- a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp
+++ b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp
@@ -5,6 +5,7 @@
#include
#include
#include
+#include
#include
static peg::parser search(R"(
@@ -19,7 +20,7 @@ SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart
QueryPart <- NotQuery / SetQuery / RarityQuery / CMCQuery / FormatQuery / PowerQuery / ToughnessQuery / ColorQuery / TypeQuery / OracleQuery / FieldQuery / GenericQuery
NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart
-SetQuery <- ('e'/'set') [:] FlexStringValue
+SetQuery <- ('e'/'set') SetExpression / ([:] FlexStringValue)
OracleQuery <- 'o' [:] MatcherString
@@ -64,6 +65,8 @@ RegexMatcherString <- ('\\/' / !'/' .)+
FlexStringValue <- CompactStringSet / String / [(] StringList [)]
CompactStringSet <- StringListString ([,+] StringListString)+
+SetExpression <- NumericOperator ws? String
+
NumericExpression <- NumericOperator ws? NumericValue
NumericOperator <- [=:] / <[>
NumericValue <- [0-9]+
@@ -101,12 +104,25 @@ static void setupParserRules()
return [=](const CardData &x) -> bool { return matcher(x->getCardType()); };
};
search["SetQuery"] = [](const peg::SemanticValues &sv) -> Filter {
- auto matcher = std::any_cast(sv[0]);
- return [=](const CardData &x) -> bool {
- QList sets = x->getSets().keys();
+ if (sv.choice() == 1) {
+ auto matcher = std::any_cast(sv[0]);
+ return [=](const CardData &x) -> bool {
+ QList sets = x->getSets().keys();
- auto matchesSet = [&matcher](const QString &set) { return matcher(set); };
- return std::any_of(sets.begin(), sets.end(), matchesSet);
+ auto matchesSet = [&matcher](const QString &set) { return matcher(set); };
+ return std::any_of(sets.begin(), sets.end(), matchesSet);
+ };
+ }
+
+ auto matcher = std::any_cast(sv[0]);
+ return [=](const CardData &x) -> bool {
+ const auto &sets = x->getSets().values();
+ auto matchesSet = [&](const PrintingInfo &printing) {
+ return printing.getSet()->getEnabled() && matcher(printing.getSet()->getReleaseDate().toJulianDay());
+ };
+ return std::any_of(sets.begin(), sets.end(), [&](const auto &printings) {
+ return std::any_of(printings.begin(), printings.end(), matchesSet);
+ });
};
};
search["Rarity"] = [](const peg::SemanticValues &sv) -> QString {
@@ -247,40 +263,54 @@ static void setupParserRules()
return QString::fromStdString(std::string(sv.sv()));
};
- search["NumericExpression"] = [](const peg::SemanticValues &sv) -> NumberMatcher {
- const auto arg = std::any_cast(sv[1]);
- const auto op = std::any_cast(sv[0]);
+ search["NumericOperator"] = [](const peg::SemanticValues &sv) -> NumberComparer {
+ const auto op = QString::fromStdString(std::string(sv.sv()));
if (op == ">") {
- return [=](const int s) { return s > arg; };
+ return [=](const int s, const int arg) { return s > arg; };
}
if (op == ">=") {
- return [=](const int s) { return s >= arg; };
+ return [=](const int s, const int arg) { return s >= arg; };
}
if (op == "<") {
- return [=](const int s) { return s < arg; };
+ return [=](const int s, const int arg) { return s < arg; };
}
if (op == "<=") {
- return [=](const int s) { return s <= arg; };
+ return [=](const int s, const int arg) { return s <= arg; };
}
if (op == "=") {
- return [=](const int s) { return s == arg; };
+ return [=](const int s, const int arg) { return s == arg; };
}
if (op == ":") {
- return [=](const int s) { return s == arg; };
+ return [=](const int s, const int arg) { return s == arg; };
}
if (op == "!=") {
- return [=](const int s) { return s != arg; };
+ return [=](const int s, const int arg) { return s != arg; };
}
- return [](int) { return false; };
+ return [](int, int) { return false; };
};
search["NumericValue"] = [](const peg::SemanticValues &sv) -> int {
return QString::fromStdString(std::string(sv.sv())).toInt();
};
- search["NumericOperator"] = [](const peg::SemanticValues &sv) -> QString {
- return QString::fromStdString(std::string(sv.sv()));
+ search["NumericExpression"] = [](const peg::SemanticValues &sv) -> NumberMatcher {
+ const auto comparer = std::any_cast(sv[0]);
+ const auto arg = std::any_cast(sv[1]);
+ return [=](int s) { return comparer(s, arg); };
+ };
+
+ search["SetExpression"] = [](const peg::SemanticValues &sv) -> NumberMatcher {
+ const auto comparer = std::any_cast(sv[0]);
+ const auto setCode = std::any_cast(sv[1]);
+ const auto allSets = CardDatabaseManager::getInstance()->getSetList();
+ for (auto &set : allSets) {
+ if (set->getShortName() == setCode) {
+ const int releaseDate = set->getReleaseDate().toJulianDay();
+ return [=](int s) { return comparer(s, releaseDate); };
+ }
+ }
+ return [](int) { return false; };
};
search["NormalMatcher"] = [](const peg::SemanticValues &sv) -> StringMatcher {
diff --git a/libcockatrice_filters/libcockatrice/filters/filter_string.h b/libcockatrice_filters/libcockatrice/filters/filter_string.h
index 71a99f7b5..a058f7d07 100644
--- a/libcockatrice_filters/libcockatrice/filters/filter_string.h
+++ b/libcockatrice_filters/libcockatrice/filters/filter_string.h
@@ -22,6 +22,7 @@ typedef CardInfoPtr CardData;
typedef std::function Filter;
typedef std::function StringMatcher;
typedef std::function NumberMatcher;
+typedef std::function NumberComparer;
namespace peg
{