summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Andres Klode <jak@debian.org>2020-02-03 13:26:41 +0000
committerJulian Andres Klode <jak@debian.org>2020-02-03 13:26:41 +0000
commit3ee6fdc08a91ef65e5a69f8857de3bf86a8fe1ad (patch)
treed7f2bed62c67b05e01095865056634db0f9b6be7
parentefc52f1fc9acb6ec815f695506bc8d5045c76834 (diff)
parent404771d0ec11f26a0b631018719e2918a049455b (diff)
Merge branch 'pu/short-patterns' into 'master'
Implement short patterns See merge request apt-team/apt!100
-rw-r--r--apt-pkg/cachefilter-patterns.cc245
-rw-r--r--apt-pkg/cachefilter-patterns.h15
-rw-r--r--apt-pkg/cacheset.cc2
-rw-r--r--apt-pkg/contrib/string_view.h5
-rw-r--r--apt-private/private-list.cc4
-rw-r--r--doc/apt-patterns.7.xml53
-rw-r--r--test/libapt/pattern_test.cc139
7 files changed, 399 insertions, 64 deletions
diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc
index a9f76ff1d..5a58a9767 100644
--- a/apt-pkg/cachefilter-patterns.cc
+++ b/apt-pkg/cachefilter-patterns.cc
@@ -17,6 +17,32 @@ namespace APT
namespace Internal
{
+static const constexpr struct
+{
+ APT::StringView shortName;
+ APT::StringView longName;
+ bool takesArgument;
+} shortPatterns[] = {
+ {"r"_sv, "?architecture"_sv, true},
+ {"A"_sv, "?archive"_sv, true},
+ {"M"_sv, "?automatic"_sv, false},
+ {"b"_sv, "?broken"_sv, false},
+ {"c"_sv, "?config-files"_sv, false},
+ {"E"_sv, "?essential"_sv, false},
+ {"F"_sv, "?false"_sv, false},
+ {"g"_sv, "?garbage"_sv, false},
+ {"i"_sv, "?installed"_sv, false},
+ {"n"_sv, "?name"_sv, true},
+ {"o"_sv, "?obsolete"_sv, false},
+ {"O"_sv, "?origin"_sv, true},
+ {"s"_sv, "?section"_sv, true},
+ {"e"_sv, "?source-package"_sv, true},
+ {"T"_sv, "?true"_sv, false},
+ {"U"_sv, "?upgradable"_sv, false},
+ {"V"_sv, "?version"_sv, true},
+ {"v"_sv, "?virtual"_sv, false},
+};
+
template <class... Args>
std::string rstrprintf(Args... args)
{
@@ -32,41 +58,197 @@ std::unique_ptr<PatternTreeParser::Node> PatternTreeParser::parseTop()
auto node = parse();
skipSpace();
+ if (node == nullptr)
+ throw Error{Node{0, sentence.size()}, "Expected pattern"};
+
if (node->end != sentence.size())
+ throw Error{Node{node->end, sentence.size()}, "Expected end of file"};
+
+ return node;
+}
+
+// Parse any pattern
+std::unique_ptr<PatternTreeParser::Node> PatternTreeParser::parse()
+{
+ return parseOr();
+}
+
+std::unique_ptr<PatternTreeParser::Node> PatternTreeParser::parseOr()
+{
+ auto start = state.offset;
+ std::vector<std::unique_ptr<PatternTreeParser::Node>> nodes;
+
+ auto firstNode = parseAnd();
+
+ if (firstNode == nullptr)
+ return nullptr;
+
+ nodes.push_back(std::move(firstNode));
+ for (skipSpace(); sentence[state.offset] == '|'; skipSpace())
+ {
+ state.offset++;
+ skipSpace();
+ auto node = parseAnd();
+
+ if (node == nullptr)
+ throw Error{Node{state.offset, sentence.size()}, "Expected pattern after |"};
+
+ nodes.push_back(std::move(node));
+ }
+
+ if (nodes.size() == 0)
+ return nullptr;
+ if (nodes.size() == 1)
+ return std::move(nodes[0]);
+
+ auto node = std::make_unique<PatternNode>();
+ node->start = start;
+ node->end = nodes[nodes.size() - 1]->end;
+ node->term = "?or";
+ node->arguments = std::move(nodes);
+ node->haveArgumentList = true;
+
+ return node;
+}
+
+std::unique_ptr<PatternTreeParser::Node> PatternTreeParser::parseAnd()
+{
+ auto start = state.offset;
+ std::vector<std::unique_ptr<PatternTreeParser::Node>> nodes;
+
+ for (skipSpace(); state.offset < sentence.size(); skipSpace())
{
- Node node2;
+ auto node = parseUnary();
+
+ if (node == nullptr)
+ break;
- node2.start = node->end;
- node2.end = sentence.size();
- throw Error{node2, "Expected end of file"};
+ nodes.push_back(std::move(node));
}
+ if (nodes.size() == 0)
+ return nullptr;
+ if (nodes.size() == 1)
+ return std::move(nodes[0]);
+
+ auto node = std::make_unique<PatternNode>();
+ node->start = start;
+ node->end = nodes[nodes.size() - 1]->end;
+ node->term = "?and";
+ node->arguments = std::move(nodes);
+ node->haveArgumentList = true;
+
return node;
}
-// Parse any pattern
-std::unique_ptr<PatternTreeParser::Node> PatternTreeParser::parse()
+std::unique_ptr<PatternTreeParser::Node> PatternTreeParser::parseUnary()
+{
+
+ if (sentence[state.offset] != '!')
+ return parsePrimary();
+
+ auto start = ++state.offset;
+ auto primary = parsePrimary();
+
+ if (primary == nullptr)
+ throw Error{Node{start, sentence.size()}, "Expected pattern"};
+
+ auto node = std::make_unique<PatternNode>();
+ node->start = start;
+ node->end = primary->end;
+ node->term = "?not";
+ node->arguments.push_back(std::move(primary));
+ node->haveArgumentList = true;
+ return node;
+}
+
+std::unique_ptr<PatternTreeParser::Node> PatternTreeParser::parsePrimary()
{
std::unique_ptr<Node> node;
+ if ((node = parseShortPattern()) != nullptr)
+ return node;
if ((node = parsePattern()) != nullptr)
return node;
+ if ((node = parseGroup()) != nullptr)
+ return node;
+
+ return nullptr;
+}
+
+std::unique_ptr<PatternTreeParser::Node> PatternTreeParser::parseGroup()
+{
+ if (sentence[state.offset] != '(')
+ return nullptr;
+
+ auto start = state.offset++;
+
+ skipSpace();
+ auto node = parse();
+ if (node == nullptr)
+ throw Error{Node{state.offset, sentence.size()},
+ "Expected pattern after '('"};
+ skipSpace();
+
+ if (sentence[state.offset] != ')')
+ throw Error{Node{state.offset, sentence.size()},
+ "Expected closing parenthesis"};
+
+ auto end = ++state.offset;
+ node->start = start;
+ node->end = end;
+ return node;
+}
+
+std::unique_ptr<PatternTreeParser::Node> PatternTreeParser::parseArgument(bool shrt)
+{
+ std::unique_ptr<Node> node;
if ((node = parseQuotedWord()) != nullptr)
return node;
- if ((node = parseWord()) != nullptr)
+ if ((node = parseWord(shrt)) != nullptr)
+ return node;
+ if ((node = parse()) != nullptr)
+ return node;
+
+ throw Error{Node{state.offset, sentence.size()},
+ "Expected pattern, quoted word, or word"};
+}
+
+// Parse a short pattern
+std::unique_ptr<PatternTreeParser::Node> PatternTreeParser::parseShortPattern()
+{
+ if (sentence[state.offset] != '~')
+ return nullptr;
+
+ for (auto &sp : shortPatterns)
+ {
+ if (sentence.substr(state.offset + 1, sp.shortName.size()) != sp.shortName)
+ continue;
+
+ auto node = std::make_unique<PatternNode>();
+ node->end = node->start = state.offset;
+ node->term = sp.longName;
+
+ state.offset += sp.shortName.size() + 1;
+ if (sp.takesArgument)
+ {
+ node->arguments.push_back(parseArgument(true));
+ node->haveArgumentList = true;
+ }
+ node->end = state.offset;
+
return node;
+ }
- Node eNode;
- eNode.end = eNode.start = state.offset;
- throw Error{eNode, "Expected pattern, quoted word, or word"};
+ throw Error{Node{state.offset, sentence.size()}, "Unknown short pattern"};
}
// Parse a list pattern (or function call pattern)
std::unique_ptr<PatternTreeParser::Node> PatternTreeParser::parsePattern()
{
- static const APT::StringView CHARS("0123456789"
- "abcdefghijklmnopqrstuvwxyz"
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "-");
+ static constexpr auto CHARS = ("0123456789"
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "-"_sv);
if (sentence[state.offset] != '?')
return nullptr;
@@ -81,6 +263,9 @@ std::unique_ptr<PatternTreeParser::Node> PatternTreeParser::parsePattern()
node->term = sentence.substr(node->start, state.offset - node->start);
+ if (node->term.size() <= 1)
+ throw Error{*node, "Pattern must have a term/name"};
+
node->end = skipSpace();
// We don't have any arguments, return node;
if (sentence[state.offset] != '(')
@@ -97,7 +282,7 @@ std::unique_ptr<PatternTreeParser::Node> PatternTreeParser::parsePattern()
return node;
}
- node->arguments.push_back(parse());
+ node->arguments.push_back(parseArgument(false));
skipSpace();
while (sentence[state.offset] == ',')
{
@@ -106,7 +291,7 @@ std::unique_ptr<PatternTreeParser::Node> PatternTreeParser::parsePattern()
// This was a trailing comma - allow it and break the loop
if (sentence[state.offset] == ')')
break;
- node->arguments.push_back(parse());
+ node->arguments.push_back(parseArgument(false));
skipSpace();
}
@@ -146,10 +331,13 @@ std::unique_ptr<PatternTreeParser::Node> PatternTreeParser::parseQuotedWord()
}
// Parse a bare word atom
-std::unique_ptr<PatternTreeParser::Node> PatternTreeParser::parseWord()
+std::unique_ptr<PatternTreeParser::Node> PatternTreeParser::parseWord(bool shrt)
{
- static const APT::StringView DISALLOWED_START("?~,()\0", 6);
- static const APT::StringView DISALLOWED(",()\0", 4);
+ static const constexpr auto DISALLOWED_START = "!?~|,() \0"_sv;
+ static const constexpr auto DISALLOWED_LONG = "|,()\0"_sv;
+ static const constexpr auto DISALLOWED_SHRT = "|,() ?\0"_sv;
+ const auto DISALLOWED = shrt ? DISALLOWED_SHRT : DISALLOWED_LONG;
+
if (DISALLOWED_START.find(sentence[state.offset]) != APT::StringView::npos)
return nullptr;
@@ -167,20 +355,21 @@ std::unique_ptr<PatternTreeParser::Node> PatternTreeParser::parseWord()
// Rendering of the tree in JSON for debugging
std::ostream &PatternTreeParser::PatternNode::render(std::ostream &os)
{
- os << "{"
- << "\"term\": \"" << term.to_string() << "\",\n"
- << "\"arguments\": [\n";
- for (auto &node : arguments)
- node->render(os) << "," << std::endl;
- os << "null]\n";
- os << "}\n";
+
+ os << term.to_string();
+ if (haveArgumentList)
+ {
+ os << "(";
+ for (auto &node : arguments)
+ node->render(os) << ",";
+ os << ")";
+ }
return os;
}
std::ostream &PatternTreeParser::WordNode::render(std::ostream &os)
{
- os << '"' << word.to_string() << '"';
- return os;
+ return quoted ? os << '"' << word.to_string() << '"' : os << word.to_string();
}
std::nullptr_t PatternTreeParser::Node::error(std::string message)
diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h
index bd8ce7e7e..4eeb68594 100644
--- a/apt-pkg/cachefilter-patterns.h
+++ b/apt-pkg/cachefilter-patterns.h
@@ -39,6 +39,8 @@ struct PatternTreeParser
size_t start = 0;
size_t end = 0;
+ explicit Node(size_t start = 0, size_t end = 0) : start(start), end(end) {}
+
virtual std::ostream &render(std::ostream &os) { return os; };
std::nullptr_t error(std::string message);
};
@@ -71,7 +73,7 @@ struct PatternTreeParser
struct State
{
- off_t offset = 0;
+ size_t offset = 0;
};
APT::StringView sentence;
@@ -90,11 +92,18 @@ struct PatternTreeParser
/// There may not be anything before or after the pattern, except for
/// whitespace.
std::unique_ptr<Node> parseTop();
+ std::unique_ptr<Node> parse(); // public for test cases only
private:
- std::unique_ptr<Node> parse();
+ std::unique_ptr<Node> parseOr();
+ std::unique_ptr<Node> parseAnd();
+ std::unique_ptr<Node> parseUnary();
+ std::unique_ptr<Node> parsePrimary();
+ std::unique_ptr<Node> parseGroup();
std::unique_ptr<Node> parsePattern();
- std::unique_ptr<Node> parseWord();
+ std::unique_ptr<Node> parseShortPattern();
+ std::unique_ptr<Node> parseArgument(bool shrt);
+ std::unique_ptr<Node> parseWord(bool shrt);
std::unique_ptr<Node> parseQuotedWord();
};
diff --git a/apt-pkg/cacheset.cc b/apt-pkg/cacheset.cc
index f5251eda8..ae1d5ee3e 100644
--- a/apt-pkg/cacheset.cc
+++ b/apt-pkg/cacheset.cc
@@ -295,7 +295,7 @@ bool CacheSetHelper::PackageFromPackageName(PackageContainerInterface * const pc
bool CacheSetHelper::PackageFromPattern(PackageContainerInterface *const pci, pkgCacheFile &Cache, std::string const &pattern)
{
- if (pattern.size() < 1 || pattern[0] != '?')
+ if (pattern.size() < 1 || (pattern[0] != '?' && pattern[0] != '~'))
return false;
auto compiledPattern = APT::CacheFilter::ParsePattern(pattern, &Cache);
diff --git a/apt-pkg/contrib/string_view.h b/apt-pkg/contrib/string_view.h
index f4f0c645f..05aad3327 100644
--- a/apt-pkg/contrib/string_view.h
+++ b/apt-pkg/contrib/string_view.h
@@ -123,7 +123,10 @@ static inline int StringViewCompareFast(StringView a, StringView b) {
return memcmp(a.data(), b.data(), a.size());
}
-
+static constexpr inline APT::StringView operator""_sv(const char *data, size_t size)
+{
+ return APT::StringView(data, size);
+}
}
inline bool operator ==(const char *other, APT::StringView that);
diff --git a/apt-private/private-list.cc b/apt-private/private-list.cc
index 6071129a7..f5c31bbcd 100644
--- a/apt-private/private-list.cc
+++ b/apt-private/private-list.cc
@@ -48,8 +48,8 @@ class PackageNameMatcher : public Matcher
{
std::string pattern = patterns[i];
APT::CacheFilter::Matcher *cachefilter = NULL;
- if (pattern.size() > 0 && pattern[0] == '?')
- cachefilter = APT::CacheFilter::ParsePattern(pattern, &cacheFile).release();
+ if (pattern.size() > 0 && (pattern[0] == '?' || pattern[0] == '~'))
+ cachefilter = APT::CacheFilter::ParsePattern(pattern, &cacheFile).release();
else if(_config->FindB("APT::Cmd::Use-Regexp", false) == true)
cachefilter = new APT::CacheFilter::PackageNameMatchesRegEx(pattern);
else
diff --git a/doc/apt-patterns.7.xml b/doc/apt-patterns.7.xml
index b94a9b226..6fa945058 100644
--- a/doc/apt-patterns.7.xml
+++ b/doc/apt-patterns.7.xml
@@ -43,19 +43,19 @@
patterns.
</para>
<variablelist>
- <varlistentry><term><code>?and(PATTERN, PATTERN, ...)</code></term>
+ <varlistentry><term><code>?and(PATTERN, PATTERN, ...)</code></term><term><code>PATTERN PATTERN ...</code></term>
<listitem><para>Selects objects where all specified patterns match.</para></listitem>
</varlistentry>
- <varlistentry><term><code>?false</code></term>
+ <varlistentry><term><code>?false</code></term><term><code>~F</code></term>
<listitem><para>Selects nothing.</para></listitem>
</varlistentry>
- <varlistentry><term><code>?not(PATTERN)</code></term>
+ <varlistentry><term><code>?not(PATTERN)</code></term><term><code>!PATTERN</code></term>
<listitem><para>Selects objects where PATTERN does not match.</para></listitem>
</varlistentry>
<varlistentry><term><code>?or(PATTERN, PATTERN, ...)</code></term>
<listitem><para>Selects objects where at least one of the specified patterns match.</para></listitem>
</varlistentry>
- <varlistentry><term><code>?true</code></term>
+ <varlistentry><term><code>?true</code></term><term><code>~T</code></term>
<listitem><para>Selects all objects.</para></listitem>
</varlistentry>
</variablelist>
@@ -83,40 +83,40 @@
These patterns select specific packages.
</para>
<variablelist>
- <varlistentry><term><code>?architecture(WILDCARD)</code></term>
+ <varlistentry><term><code>?architecture(WILDCARD)</code></term><term><code>~rWILDCARD</code></term>
<listitem><para>Selects packages matching the specified architecture, which may contain wildcards using any.</para></listitem>
</varlistentry>
- <varlistentry><term><code>?automatic</code></term>
+ <varlistentry><term><code>?automatic</code></term><term><code>~M</code></term>
<listitem><para>Selects packages that were installed automatically.</para></listitem>
</varlistentry>
- <varlistentry><term><code>?broken</code></term>
+ <varlistentry><term><code>?broken</code></term><term><code>~b</code></term>
<listitem><para>Selects packages that have broken dependencies.</para></listitem>
</varlistentry>
- <varlistentry><term><code>?config-files</code></term>
+ <varlistentry><term><code>?config-files</code></term><term><code>~c</code></term>
<listitem><para>Selects packages that are not fully installed, but have solely residual configuration files left.</para></listitem>
</varlistentry>
- <varlistentry><term><code>?essential</code></term>
+ <varlistentry><term><code>?essential</code></term><term><code>~E</code></term>
<listitem><para>Selects packages that have Essential: yes set in their control file.</para></listitem>
</varlistentry>
<varlistentry><term><code>?exact-name(NAME)</code></term>
<listitem><para>Selects packages with the exact specified name.</para></listitem>
</varlistentry>
- <varlistentry><term><code>?garbage</code></term>
+ <varlistentry><term><code>?garbage</code></term><term><code>~g</code></term>
<listitem><para>Selects packages that can be removed automatically.</para></listitem>
</varlistentry>
- <varlistentry><term><code>?installed</code></term>
+ <varlistentry><term><code>?installed</code></term><term><code>~i</code></term>
<listitem><para>Selects packages that are currently installed.</para></listitem>
</varlistentry>
- <varlistentry><term><code>?name(REGEX)</code></term>
+ <varlistentry><term><code>?name(REGEX)</code></term><term><code>~nREGEX</code></term>
<listitem><para>Selects packages where the name matches the given regular expression.</para></listitem>
</varlistentry>
- <varlistentry><term><code>?obsolete</code></term>
+ <varlistentry><term><code>?obsolete</code></term><term><code>~o</code></term>
<listitem><para>Selects packages that no longer exist in repositories.</para></listitem>
</varlistentry>
- <varlistentry><term><code>?upgradable</code></term>
+ <varlistentry><term><code>?upgradable</code></term><term><code>~U</code></term>
<listitem><para>Selects packages that can be upgraded (have a newer candidate).</para></listitem>
</varlistentry>
- <varlistentry><term><code>?virtual</code></term>
+ <varlistentry><term><code>?virtual</code></term><term><code>~v</code></term>
<listitem><para>Selects all virtual packages; that is packages without a version.
These exist when they are referenced somewhere in the archive,
for example because something depends on that name.</para></listitem>
@@ -129,22 +129,22 @@
These patterns select specific versions of a package.
</para>
<variablelist>
- <varlistentry><term><code>?archive(REGEX)</code></term>
+ <varlistentry><term><code>?archive(REGEX)</code></term><term><code>~AREGEX</code></term>
<listitem><para>Selects versions that come from the archive that matches the specified regular expression. Archive, here, means the values after <code>a=</code> in <command>apt-cache policy</command>.</para></listitem>
</varlistentry>
- <varlistentry><term><code>?origin(REGEX)</code></term>
+ <varlistentry><term><code>?origin(REGEX)</code></term><term><code>~OREGEX</code></term>
<listitem><para>Selects versions that come from the origin that matches the specified regular expression. Origin, here, means the values after <code>o=</code> in <command>apt-cache policy</command>.</para></listitem>
</varlistentry>
- <varlistentry><term><code>?section(REGEX)</code></term>
+ <varlistentry><term><code>?section(REGEX)</code></term><term><code>~sREGEX</code></term>
<listitem><para>Selects versions where the section matches the specified regular expression.</para></listitem>
</varlistentry>
- <varlistentry><term><code>?source-package(REGEX)</code></term>
+ <varlistentry><term><code>?source-package(REGEX)</code></term><term><code>~eREGEX</code></term>
<listitem><para>Selects versions where the source package name matches the specified regular expression.</para></listitem>
</varlistentry>
<varlistentry><term><code>?source-version(REGEX)</code></term>
<listitem><para>Selects versions where the source package version matches the specified regular expression.</para></listitem>
</varlistentry>
- <varlistentry><term><code>?version(REGEX)</code></term>
+ <varlistentry><term><code>?version(REGEX)</code></term><term><code>~VREGEX</code></term>
<listitem><para>Selects versions where the version string matching the specified regular expression.</para></listitem>
</varlistentry>
</variablelist>
@@ -168,9 +168,6 @@
</para>
<itemizedlist>
<listitem>
- <para>Only long forms &mdash; the ones starting with ? &mdash; are supported</para>
- </listitem>
- <listitem>
<para>
Syntax is uniform: If there is an opening parenthesis after a term, it is always assumed to be the beginning of an argument list.
</para>
@@ -193,6 +190,16 @@
<listitem>
<para>?narrow accepts infinite arguments</para>
</listitem>
+ <listitem>
+ <para><code>foo</code> cannot be used as a shortform for <code>?name(foo)</code>, as this can cause typos to go unnoticed:
+ Consider <code>?and(...,~poptional)</code>:
+ this requires the package to have <code>required</code> priority, but
+ if you do not type the <code>~</code>, it would require the package name to contain <code>poptional</code>.</para>
+ </listitem>
+ <listitem>
+ <para>Grouping patterns with <code>(...)</code> or writing <code>?or(A,B)</code> as <code>A|B</code> are not supported. We
+ do not believe that the use of <code>|</code> is that common, and the grouping is not necessary without it.</para>
+ </listitem>
</itemizedlist>
</refsect1>
diff --git a/test/libapt/pattern_test.cc b/test/libapt/pattern_test.cc
index de2fbceb9..bfcaf2093 100644
--- a/test/libapt/pattern_test.cc
+++ b/test/libapt/pattern_test.cc
@@ -14,21 +14,53 @@
using namespace APT::Internal;
+#define EXPECT_EXCEPTION(exp, exc, msg) \
+ caught = false; \
+ try \
+ { \
+ exp; \
+ } \
+ catch (exc & e) \
+ { \
+ caught = true; \
+ EXPECT_TRUE(e.message.find(msg) != std::string::npos) << msg << " not in " << e.message; \
+ }; \
+ EXPECT_TRUE(caught) << #exp "should have thrown an exception"
+
+TEST(TreeParserTest, ParseInvalid)
+{
+ bool caught = false;
+
+ // Not a valid pattern: Reject
+ EXPECT_EXCEPTION(PatternTreeParser("?").parse(), PatternTreeParser::Error, "Pattern must have a term");
+ EXPECT_EXCEPTION(PatternTreeParser("?AB?").parse(), PatternTreeParser::Error, "Pattern must have a term");
+ EXPECT_EXCEPTION(PatternTreeParser("~").parse(), PatternTreeParser::Error, "Unknown short pattern");
+
+ // Not a pattern at all: Report nullptr
+ EXPECT_EQ(PatternTreeParser("A?").parse(), nullptr);
+}
+
TEST(TreeParserTest, ParseWord)
{
- auto node = PatternTreeParser("word").parseTop();
- auto wordNode = dynamic_cast<PatternTreeParser::WordNode *>(node.get());
+ auto node = PatternTreeParser("?word(word)").parseTop();
+ auto patternNode = dynamic_cast<PatternTreeParser::PatternNode *>(node.get());
- EXPECT_EQ(node.get(), wordNode);
+ ASSERT_EQ(patternNode->arguments.size(), 1u);
+ auto wordNode = dynamic_cast<PatternTreeParser::WordNode *>(patternNode->arguments[0].get());
+
+ EXPECT_EQ(patternNode->arguments[0].get(), wordNode);
EXPECT_EQ(wordNode->word, "word");
}
TEST(TreeParserTest, ParseQuotedWord)
{
- auto node = PatternTreeParser("\"a word\"").parseTop();
- auto wordNode = dynamic_cast<PatternTreeParser::WordNode *>(node.get());
+ auto node = PatternTreeParser("?word(\"a word\")").parseTop();
+ auto patternNode = dynamic_cast<PatternTreeParser::PatternNode *>(node.get());
- EXPECT_EQ(node.get(), wordNode);
+ ASSERT_EQ(patternNode->arguments.size(), 1u);
+ auto wordNode = dynamic_cast<PatternTreeParser::WordNode *>(patternNode->arguments[0].get());
+
+ EXPECT_EQ(patternNode->arguments[0].get(), wordNode);
EXPECT_EQ(wordNode->word, "a word");
}
@@ -93,3 +125,98 @@ TEST(TreeParserTest, ParseWithManyArgsWithSpacesWithTrailingComma)
EXPECT_EQ(patternNode->term, "?hello");
EXPECT_EQ(2u, patternNode->arguments.size());
}
+
+// Helper
+static bool samePattern(const std::unique_ptr<PatternTreeParser::Node> &a, const std::unique_ptr<PatternTreeParser::Node> &b)
+{
+ auto pa = dynamic_cast<const PatternTreeParser::PatternNode *>(a.get());
+ auto pb = dynamic_cast<const PatternTreeParser::PatternNode *>(b.get());
+
+ if (pa && pb)
+ {
+ if (pa->term != pb->term || pa->haveArgumentList != pb->haveArgumentList || pa->arguments.size() != pb->arguments.size())
+ return false;
+
+ for (size_t i = 0; i < pa->arguments.size(); i++)
+ {
+ if (!samePattern(pa->arguments[i], pb->arguments[i]))
+ return false;
+ }
+ return true;
+ }
+
+ auto wa = dynamic_cast<const PatternTreeParser::WordNode *>(a.get());
+ auto wb = dynamic_cast<const PatternTreeParser::WordNode *>(b.get());
+ if (wa && wb)
+ return wa->word == wb->word && wa->quoted == wb->quoted;
+
+ return false;
+}
+
+#define EXPECT_PATTERN_EQ(shrt, lng) \
+ EXPECT_TRUE(samePattern(PatternTreeParser(shrt).parseTop(), PatternTreeParser(lng).parseTop()))
+#define EXPECT_PATTERN_EQ_ATOMIC(shrt, lng) \
+ EXPECT_TRUE(PatternTreeParser(shrt).parseTop()); \
+ caught = false; \
+ try \
+ { \
+ PatternTreeParser(shrt "XXX").parseTop(); \
+ } \
+ catch (PatternTreeParser::Error & e) \
+ { \
+ caught = true; \
+ }; \
+ EXPECT_TRUE(caught) << shrt "XXX should have thrown an exception"; \
+ EXPECT_PATTERN_EQ(shrt, lng)
+
+TEST(TreeParserTest, ParseShortPattern)
+{
+ bool caught;
+ EXPECT_PATTERN_EQ("~ramd64", "?architecture(amd64)");
+ EXPECT_PATTERN_EQ("~AanArchive", "?archive(anArchive)");
+ EXPECT_PATTERN_EQ_ATOMIC("~M", "?automatic");
+ EXPECT_PATTERN_EQ_ATOMIC("~b", "?broken");
+ EXPECT_PATTERN_EQ_ATOMIC("~c", "?config-files");
+ EXPECT_PATTERN_EQ_ATOMIC("~E", "?essential");
+ EXPECT_PATTERN_EQ_ATOMIC("~F", "?false");
+ EXPECT_PATTERN_EQ_ATOMIC("~g", "?garbage");
+ EXPECT_PATTERN_EQ_ATOMIC("~i", "?installed");
+ EXPECT_PATTERN_EQ("~napt", "?name(apt)");
+ EXPECT_PATTERN_EQ_ATOMIC("~o", "?obsolete");
+ EXPECT_PATTERN_EQ("~Obar", "?origin(bar)");
+ EXPECT_PATTERN_EQ("~sfoo", "?section(foo)");
+ EXPECT_PATTERN_EQ("~esourcename", "?source-package(sourcename)");
+ EXPECT_PATTERN_EQ_ATOMIC("~T", "?true");
+ EXPECT_PATTERN_EQ_ATOMIC("~U", "?upgradable");
+ EXPECT_PATTERN_EQ("~Vverstr", "?version(verstr)");
+ EXPECT_PATTERN_EQ_ATOMIC("~v", "?virtual");
+ EXPECT_PATTERN_EQ("!?foo", "?not(?foo)");
+
+ caught = false;
+ try
+ {
+ PatternTreeParser("!x").parseTop();
+ }
+ catch (PatternTreeParser::Error &e)
+ {
+ caught = true;
+ };
+ EXPECT_TRUE(caught) << "!X should have thrown an exception";
+
+ EXPECT_PATTERN_EQ("?a?b", "?and(?a, ?b)");
+ EXPECT_PATTERN_EQ("~T~F", "?and(?true, ?false)");
+ EXPECT_PATTERN_EQ("~T ~F", "?and(?true, ?false)");
+ EXPECT_PATTERN_EQ("~T !~F", "?and(?true, ?not(?false))");
+ EXPECT_PATTERN_EQ("!~F ~T", "?and(?not(?false), ?true)");
+ EXPECT_PATTERN_EQ("!~F~T", "?and(?not(?false), ?true)");
+
+ EXPECT_PATTERN_EQ("!~F~T | ~T", "?or(?and(?not(?false), ?true), ?true)");
+ EXPECT_PATTERN_EQ("~ramd64|~rall", "?or(?architecture(amd64), ?architecture(all))");
+ EXPECT_PATTERN_EQ("~ramd64 | ~rall", "?or(?architecture(amd64), ?architecture(all))");
+ EXPECT_PATTERN_EQ("~ramd64?name(foo)", "?and(?architecture(amd64), ?name(foo))");
+
+ EXPECT_PATTERN_EQ("(?A|?B)?C", "?and(?or(?A, ?B), ?C)");
+ EXPECT_PATTERN_EQ("?A|?B?C", "?or(?A, ?and(?B, ?C))");
+ EXPECT_PATTERN_EQ("?A|(?B?C)", "?or(?A, ?and(?B, ?C))");
+ EXPECT_PATTERN_EQ("(?B?C)|?A", "?or(?and(?B, ?C), ?A)");
+}