From fd43b1694f1382a3a47f5dc546ebe3d39fcd6e7d Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Mon, 20 Jan 2020 14:14:49 +0100 Subject: Implement short patterns (patterns starting with ~) Also make pattern detector in cacheset and private's list accept such patterns. We probably should just try to parse and see if it is a (start of a) pattern. --- apt-pkg/cachefilter-patterns.cc | 57 +++++++++++++++++++++++++++++++++++ apt-pkg/cachefilter-patterns.h | 1 + apt-pkg/cacheset.cc | 2 +- apt-private/private-list.cc | 4 +-- doc/apt-patterns.7.xml | 36 +++++++++++----------- test/libapt/pattern_test.cc | 66 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 145 insertions(+), 21 deletions(-) diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index 7abc4536d..11ad5d723 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 std::string rstrprintf(Args... args) { @@ -42,6 +68,8 @@ std::unique_ptr PatternTreeParser::parseTop() std::unique_ptr PatternTreeParser::parse() { std::unique_ptr node; + if ((node = parseShortPattern()) != nullptr) + return node; if ((node = parsePattern()) != nullptr) return node; if ((node = parseQuotedWord()) != nullptr) @@ -53,6 +81,35 @@ std::unique_ptr PatternTreeParser::parse() "Expected pattern, quoted word, or word"}; } +// Parse a short pattern +std::unique_ptr 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(); + node->end = node->start = state.offset; + node->term = sp.longName; + + state.offset += sp.shortName.size() + 1; + if (sp.takesArgument) + { + node->arguments.push_back(parse()); + node->haveArgumentList = true; + } + node->end = state.offset; + + return node; + } + + throw Error{Node{state.offset, sentence.size()}, "Unknown short pattern"}; +} + // Parse a list pattern (or function call pattern) std::unique_ptr PatternTreeParser::parsePattern() { diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h index 8317665bc..0d6e9d99e 100644 --- a/apt-pkg/cachefilter-patterns.h +++ b/apt-pkg/cachefilter-patterns.h @@ -96,6 +96,7 @@ struct PatternTreeParser private: std::unique_ptr parse(); std::unique_ptr parsePattern(); + std::unique_ptr parseShortPattern(); std::unique_ptr parseWord(); std::unique_ptr 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-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..f18fe6a19 100644 --- a/doc/apt-patterns.7.xml +++ b/doc/apt-patterns.7.xml @@ -46,7 +46,7 @@ ?and(PATTERN, PATTERN, ...) Selects objects where all specified patterns match. - ?false + ?false~F Selects nothing. ?not(PATTERN) @@ -55,7 +55,7 @@ ?or(PATTERN, PATTERN, ...) Selects objects where at least one of the specified patterns match. - ?true + ?true~T Selects all objects. @@ -83,40 +83,40 @@ These patterns select specific packages. - ?architecture(WILDCARD) + ?architecture(WILDCARD)~rWILDCARD Selects packages matching the specified architecture, which may contain wildcards using any. - ?automatic + ?automatic~M Selects packages that were installed automatically. - ?broken + ?broken~b Selects packages that have broken dependencies. - ?config-files + ?config-files~c Selects packages that are not fully installed, but have solely residual configuration files left. - ?essential + ?essential~E Selects packages that have Essential: yes set in their control file. ?exact-name(NAME) Selects packages with the exact specified name. - ?garbage + ?garbage~g Selects packages that can be removed automatically. - ?installed + ?installed~i Selects packages that are currently installed. - ?name(REGEX) + ?name(REGEX)~nREGEX Selects packages where the name matches the given regular expression. - ?obsolete + ?obsolete~o Selects packages that no longer exist in repositories. - ?upgradable + ?upgradable~U Selects packages that can be upgraded (have a newer candidate). - ?virtual + ?virtual~v 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. @@ -129,22 +129,22 @@ These patterns select specific versions of a package. - ?archive(REGEX) + ?archive(REGEX)~AREGEX Selects versions that come from the archive that matches the specified regular expression. Archive, here, means the values after a= in apt-cache policy. - ?origin(REGEX) + ?origin(REGEX)~OREGEX Selects versions that come from the origin that matches the specified regular expression. Origin, here, means the values after o= in apt-cache policy. - ?section(REGEX) + ?section(REGEX)~sREGEX Selects versions where the section matches the specified regular expression. - ?source-package(REGEX) + ?source-package(REGEX)~eREGEX Selects versions where the source package name matches the specified regular expression. ?source-version(REGEX) Selects versions where the source package version matches the specified regular expression. - ?version(REGEX) + ?version(REGEX)~VREGEX Selects versions where the version string matching the specified regular expression. diff --git a/test/libapt/pattern_test.cc b/test/libapt/pattern_test.cc index de2fbceb9..492a29eac 100644 --- a/test/libapt/pattern_test.cc +++ b/test/libapt/pattern_test.cc @@ -93,3 +93,69 @@ TEST(TreeParserTest, ParseWithManyArgsWithSpacesWithTrailingComma) EXPECT_EQ(patternNode->term, "?hello"); EXPECT_EQ(2u, patternNode->arguments.size()); } + +// Helper +static bool samePattern(const std::unique_ptr &a, const std::unique_ptr &b) +{ + auto pa = dynamic_cast(a.get()); + auto pb = dynamic_cast(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(a.get()); + auto wb = dynamic_cast(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"); +} -- cgit v1.2.3