From 690ff4c3e44e7063ebde2557b7c0087ab720b894 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Wed, 14 Aug 2019 14:38:26 +0200 Subject: Add initial support for parsing patterns into parse trees Introduce a parser for patterns that generates a parse tree. The language understood by the parser is: pattern = '?'TERM | '?'TERM '(' pattern (',' pattern)* ','? ')' | WORD | QUOTED-WORD TERM = [0-9a-zA-Z-] WORD = [0-9a-ZA-Z-.*^$\[\]_\\] QUOTED_WORD = "..." # you know what I mean This language is context free, which is a massive simplification from aptitude's language, where ?foo(bar) could have two different meanings depending on whether ?foo takes an argument or not. --- apt-pkg/cachefilter-patterns.cc | 205 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 apt-pkg/cachefilter-patterns.cc (limited to 'apt-pkg/cachefilter-patterns.cc') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc new file mode 100644 index 000000000..3c958ebae --- /dev/null +++ b/apt-pkg/cachefilter-patterns.cc @@ -0,0 +1,205 @@ +/* + * cachefilter-patterns.cc - Parser for aptitude-style patterns + * + * Copyright (c) 2019 Canonical Ltd + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include + +namespace APT +{ +namespace Internal +{ + +template +std::string rstrprintf(Args... args) +{ + std::string str; + strprintf(str, std::forward(args)...); + return str; +} + +// Parse a complete pattern, make sure it's the entire input +std::unique_ptr PatternTreeParser::parseTop() +{ + skipSpace(); + auto node = parse(); + skipSpace(); + + if (node->end != sentence.size()) + { + Node node2; + + node2.start = node->end; + node2.end = sentence.size(); + throw Error{node2, "Expected end of file"}; + } + + return node; +} + +// Parse any pattern +std::unique_ptr PatternTreeParser::parse() +{ + std::unique_ptr node; + if ((node = parsePattern()) != nullptr) + return node; + if ((node = parseQuotedWord()) != nullptr) + return node; + if ((node = parseWord()) != nullptr) + return node; + + Node eNode; + eNode.end = eNode.start = state.offset; + throw Error{eNode, "Expected pattern, quoted word, or word"}; +} + +// Parse a list pattern (or function call pattern) +std::unique_ptr PatternTreeParser::parsePattern() +{ + static const APT::StringView CHARS("0123456789" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "-"); + if (sentence[state.offset] != '?') + return nullptr; + + auto node = std::make_unique(); + node->end = node->start = state.offset; + state.offset++; + + while (CHARS.find(sentence[state.offset]) != APT::StringView::npos) + { + ++state.offset; + } + + node->term = sentence.substr(node->start, state.offset - node->start); + + node->end = skipSpace(); + // We don't have any arguments, return node; + if (sentence[state.offset] != '(') + return node; + node->end = ++state.offset; + skipSpace(); + + node->haveArgumentList = true; + + // Empty argument list, return + if (sentence[state.offset] == ')') + { + node->end = ++state.offset; + return node; + } + + node->arguments.push_back(parse()); + skipSpace(); + while (sentence[state.offset] == ',') + { + ++state.offset; + skipSpace(); + // This was a trailing comma - allow it and break the loop + if (sentence[state.offset] == ')') + break; + node->arguments.push_back(parse()); + skipSpace(); + } + + if (sentence[state.offset] != ')') + throw Error{*node, rstrprintf("Expected closing parenthesis, received %d", sentence[state.offset])}; + + node->end = ++state.offset; + return node; +} + +// Parse a quoted word atom +std::unique_ptr PatternTreeParser::parseQuotedWord() +{ + if (sentence[state.offset] != '"') + return nullptr; + + auto node = std::make_unique(); + node->start = state.offset; + + // Eat beginning of string + state.offset++; + + while (sentence[state.offset] != '"' && sentence[state.offset] != '\0') + state.offset++; + + // End of string + if (sentence[state.offset] != '"') + throw Error{*node, "Could not find end of string"}; + state.offset++; + + node->end = state.offset; + node->word = sentence.substr(node->start + 1, node->end - node->start - 2); + + return node; +} + +// Parse a bare word atom +std::unique_ptr PatternTreeParser::parseWord() +{ + static const APT::StringView CHARS("0123456789" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "-.*^$[]_\\"); + if (CHARS.find(sentence[state.offset]) == APT::StringView::npos) + return nullptr; + + auto node = std::make_unique(); + node->start = state.offset; + + while (CHARS.find(sentence[state.offset]) != APT::StringView::npos) + state.offset++; + + node->end = state.offset; + node->word = sentence.substr(node->start, node->end - node->start); + return node; +} + +// 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"; + return os; +} + +std::ostream &PatternTreeParser::WordNode::render(std::ostream &os) +{ + os << '"' << word.to_string() << '"'; + return os; +} + +std::nullptr_t PatternTreeParser::Node::error(std::string message) +{ + throw Error{*this, message}; +} + +bool PatternTreeParser::PatternNode::matches(APT::StringView name, int min, int max) +{ + if (name != term) + return false; + if (max != 0 && !haveArgumentList) + error(rstrprintf("%s expects an argument list", term.to_string().c_str())); + if (max == 0 && haveArgumentList) + error(rstrprintf("%s does not expect an argument list", term.to_string().c_str())); + if (min >= 0 && min == max && (arguments.size() != size_t(min))) + error(rstrprintf("%s expects %d arguments, but received %d arguments", term.to_string().c_str(), min, arguments.size())); + if (min >= 0 && arguments.size() < size_t(min)) + error(rstrprintf("%s expects at least %d arguments, but received %d arguments", term.to_string().c_str(), min, arguments.size())); + if (max >= 0 && arguments.size() > size_t(max)) + error(rstrprintf("%s expects at most %d arguments, but received %d arguments", term.to_string().c_str(), max, arguments.size())); + return true; +} + +} // namespace Internal +} // namespace APT -- cgit v1.2.3-70-g09d2 From 08b61197f418883ea20563e2251fb60779c0ba87 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Thu, 15 Aug 2019 11:47:00 +0200 Subject: Add pattern tree parser infra and connect with cacheset and apt list This adds a transformation from parse tree into a CacheFilter and connects it with cachesets and the apt list command. --- apt-pkg/cachefilter-patterns.cc | 51 +++++++++++++++++++++++++++++++ apt-pkg/cachefilter-patterns.h | 15 ++++++++++ apt-pkg/cachefilter.h | 4 +++ apt-pkg/cacheset.cc | 31 ++++++++++++++++++- apt-pkg/cacheset.h | 6 +++- apt-private/private-list.cc | 23 +++++++++----- test/integration/test-apt-patterns | 61 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 182 insertions(+), 9 deletions(-) create mode 100755 test/integration/test-apt-patterns (limited to 'apt-pkg/cachefilter-patterns.cc') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index 3c958ebae..ea4cf3cd8 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -201,5 +201,56 @@ bool PatternTreeParser::PatternNode::matches(APT::StringView name, int min, int return true; } +std::unique_ptr PatternParser::aPattern(std::unique_ptr &nodeP) +{ + assert(nodeP != nullptr); + auto node = dynamic_cast(nodeP.get()); + if (node == nullptr) + nodeP->error("Expected a pattern"); + + node->error(rstrprintf("Unrecognized pattern '%s'", node->term.to_string().c_str())); + + return nullptr; +} + +std::string PatternParser::aWord(std::unique_ptr &nodeP) +{ + assert(nodeP != nullptr); + auto node = dynamic_cast(nodeP.get()); + if (node == nullptr) + nodeP->error("Expected a word"); + return node->word.to_string(); +} + } // namespace Internal + +// The bridge into the public world +std::unique_ptr APT::CacheFilter::ParsePattern(APT::StringView pattern, pkgCacheFile *file) +{ + if (file != nullptr && !file->BuildDepCache()) + return nullptr; + + try + { + auto top = APT::Internal::PatternTreeParser(pattern).parseTop(); + APT::Internal::PatternParser parser{file}; + return parser.aPattern(top); + } + catch (APT::Internal::PatternTreeParser::Error &e) + { + std::stringstream ss; + ss << "input:" << e.location.start << "-" << e.location.end << ": error: " << e.message << "\n"; + ss << pattern.to_string() << "\n"; + for (size_t i = 0; i < e.location.start; i++) + ss << " "; + for (size_t i = e.location.start; i < e.location.end; i++) + ss << "^"; + + ss << "\n"; + + _error->Error("%s", ss.str().c_str()); + return nullptr; + } +} + } // namespace APT diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h index dfbcd66a5..3097ba94b 100644 --- a/apt-pkg/cachefilter-patterns.h +++ b/apt-pkg/cachefilter-patterns.h @@ -98,6 +98,21 @@ struct PatternTreeParser std::unique_ptr parseQuotedWord(); }; +/** + * \brief PatternParser parses the given sentence into a parse tree. + * + * The parse tree consists of nodes: + * - Word nodes which contains words or quoted words + * - Patterns, which represent ?foo and ?foo(...) patterns + */ +struct PatternParser +{ + pkgCacheFile *file; + + std::unique_ptr aPattern(std::unique_ptr &nodeP); + std::string aWord(std::unique_ptr &nodeP); +}; + } // namespace Internal } // namespace APT #endif diff --git a/apt-pkg/cachefilter.h b/apt-pkg/cachefilter.h index 8a6c01341..3c6e1559d 100644 --- a/apt-pkg/cachefilter.h +++ b/apt-pkg/cachefilter.h @@ -7,7 +7,9 @@ #define APT_CACHEFILTER_H // Include Files /*{{{*/ #include +#include +#include #include #include @@ -145,6 +147,8 @@ public: }; /*}}}*/ +/// \brief Parse a pattern, return nullptr or pattern +std::unique_ptr ParsePattern(APT::StringView pattern, pkgCacheFile *file); } } #endif diff --git a/apt-pkg/cacheset.cc b/apt-pkg/cacheset.cc index 789727266..dd55edb4e 100644 --- a/apt-pkg/cacheset.cc +++ b/apt-pkg/cacheset.cc @@ -46,6 +46,7 @@ bool CacheSetHelper::PackageFrom(enum PkgSelector const select, PackageContainer case FNMATCH: return PackageFromFnmatch(pci, Cache, pattern); case PACKAGENAME: return PackageFromPackageName(pci, Cache, pattern); case STRING: return PackageFromString(pci, Cache, pattern); + case PATTERN: return PackageFromPattern(pci, Cache, pattern); } return false; } @@ -281,13 +282,33 @@ bool CacheSetHelper::PackageFromPackageName(PackageContainerInterface * const pc pci->insert(Pkg); return true; } + +bool CacheSetHelper::PackageFromPattern(PackageContainerInterface *const pci, pkgCacheFile &Cache, std::string const &pattern) +{ + if (pattern.size() < 1 || pattern[0] != '?') + return false; + + auto compiledPattern = APT::CacheFilter::ParsePattern(pattern, &Cache); + if (!compiledPattern) + return false; + + for (pkgCache::PkgIterator Pkg = Cache->PkgBegin(); Pkg.end() == false; ++Pkg) + { + if ((*compiledPattern)(Pkg) == false) + continue; + + pci->insert(Pkg); + } + return true; +} /*}}}*/ // PackageFromString - Return all packages matching a specific string /*{{{*/ bool CacheSetHelper::PackageFromString(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string const &str) { bool found = true; _error->PushToStack(); - if (PackageFrom(CacheSetHelper::PACKAGENAME, pci, Cache, str) == false && + if (PackageFrom(CacheSetHelper::PATTERN, pci, Cache, str) == false && + PackageFrom(CacheSetHelper::PACKAGENAME, pci, Cache, str) == false && PackageFrom(CacheSetHelper::TASK, pci, Cache, str) == false && // FIXME: hm, hm, regexp/fnmatch incompatible? PackageFrom(CacheSetHelper::FNMATCH, pci, Cache, str) == false && @@ -686,6 +707,7 @@ void CacheSetHelper::canNotFindPackage(enum PkgSelector const select, case FNMATCH: canNotFindFnmatch(pci, Cache, pattern); break; case PACKAGENAME: canNotFindPackage(pci, Cache, pattern); break; case STRING: canNotFindPackage(pci, Cache, pattern); break; + case PATTERN: canNotFindPackage(pci, Cache, pattern); break; case UNKNOWN: break; } } @@ -822,6 +844,7 @@ void CacheSetHelper::showPackageSelection(pkgCache::PkgIterator const &pkg, enum case REGEX: showRegExSelection(pkg, pattern); break; case TASK: showTaskSelection(pkg, pattern); break; case FNMATCH: showFnmatchSelection(pkg, pattern); break; + case PATTERN: showPatternSelection(pkg, pattern); break; case PACKAGENAME: /* no surprises here */ break; case STRING: /* handled by the special cases */ break; case UNKNOWN: break; @@ -840,6 +863,12 @@ void CacheSetHelper::showRegExSelection(pkgCache::PkgIterator const &/*pkg*/, // showFnmatchSelection /*{{{*/ void CacheSetHelper::showFnmatchSelection(pkgCache::PkgIterator const &/*pkg*/, std::string const &/*pattern*/) { +} + /*}}}*/ +// showPatternSelection /*{{{*/ +void CacheSetHelper::showPatternSelection(pkgCache::PkgIterator const & /*pkg*/, + std::string const & /*pattern*/) +{ } /*}}}*/ /*}}}*/ diff --git a/apt-pkg/cacheset.h b/apt-pkg/cacheset.h index 489fb6220..6023b861d 100644 --- a/apt-pkg/cacheset.h +++ b/apt-pkg/cacheset.h @@ -52,7 +52,7 @@ public: /*{{{*/ GlobalError::MsgType ErrorType = GlobalError::ERROR); virtual ~CacheSetHelper(); - enum PkgSelector { UNKNOWN, REGEX, TASK, FNMATCH, PACKAGENAME, STRING }; + enum PkgSelector { UNKNOWN, REGEX, TASK, FNMATCH, PACKAGENAME, STRING, PATTERN }; virtual bool PackageFrom(enum PkgSelector const select, PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string const &pattern); @@ -172,10 +172,12 @@ protected: bool PackageFromFnmatch(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string pattern); bool PackageFromPackageName(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string pattern); bool PackageFromString(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string const &pattern); + bool PackageFromPattern(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string const &pattern); private: void showTaskSelection(pkgCache::PkgIterator const &pkg, std::string const &pattern); void showRegExSelection(pkgCache::PkgIterator const &pkg, std::string const &pattern); void showFnmatchSelection(pkgCache::PkgIterator const &pkg, std::string const &pattern); + void showPatternSelection(pkgCache::PkgIterator const &pkg, std::string const &pattern); void canNotFindTask(PackageContainerInterface *const pci, pkgCacheFile &Cache, std::string pattern); void canNotFindRegEx(PackageContainerInterface *const pci, pkgCacheFile &Cache, std::string pattern); void canNotFindFnmatch(PackageContainerInterface *const pci, pkgCacheFile &Cache, std::string pattern); @@ -739,6 +741,8 @@ public: std::string pkg, CacheSetHelper::VerSelector const fallback, CacheSetHelper &helper, bool const onlyFromName = false); + static bool FromPattern(VersionContainerInterface *const vci, pkgCacheFile &Cache, + std::string pkg, CacheSetHelper::VerSelector const fallback, CacheSetHelper &helper); static bool FromPackage(VersionContainerInterface * const vci, pkgCacheFile &Cache, pkgCache::PkgIterator const &P, CacheSetHelper::VerSelector const fallback, diff --git a/apt-private/private-list.cc b/apt-private/private-list.cc index 7c8c89777..6071129a7 100644 --- a/apt-private/private-list.cc +++ b/apt-private/private-list.cc @@ -39,17 +39,26 @@ struct PackageSortAlphabetic /*{{{*/ class PackageNameMatcher : public Matcher { + pkgCacheFile &cacheFile; public: - explicit PackageNameMatcher(const char **patterns) + explicit PackageNameMatcher(pkgCacheFile &cacheFile, const char **patterns) + : cacheFile(cacheFile) { for(int i=0; patterns[i] != NULL; ++i) { std::string pattern = patterns[i]; - APT::CacheFilter::PackageMatcher *cachefilter = NULL; - if(_config->FindB("APT::Cmd::Use-Regexp", false) == true) + APT::CacheFilter::Matcher *cachefilter = NULL; + if (pattern.size() > 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 cachefilter = new APT::CacheFilter::PackageNameMatchesFnmatch(pattern); + + if (cachefilter == nullptr) { + return; + filters.clear(); + } filters.push_back(cachefilter); } } @@ -62,7 +71,7 @@ class PackageNameMatcher : public Matcher { for(J=filters.begin(); J != filters.end(); ++J) { - APT::CacheFilter::PackageMatcher *cachefilter = *J; + APT::CacheFilter::Matcher *cachefilter = *J; if((*cachefilter)(P)) return true; } @@ -70,8 +79,8 @@ class PackageNameMatcher : public Matcher } private: - std::vector filters; - std::vector::const_iterator J; + std::vector filters; + std::vector::const_iterator J; #undef PackageMatcher }; /*}}}*/ @@ -111,7 +120,7 @@ bool DoList(CommandLine &Cmd) if (_config->FindB("APT::Cmd::List-Include-Summary", false) == true) format += "\n ${Description}\n"; - PackageNameMatcher matcher(patterns); + PackageNameMatcher matcher(CacheFile, patterns); LocalitySortedVersionSet bag; OpTextProgress progress(*_config); progress.OverallProgress(0, diff --git a/test/integration/test-apt-patterns b/test/integration/test-apt-patterns new file mode 100755 index 000000000..c33fa1bf8 --- /dev/null +++ b/test/integration/test-apt-patterns @@ -0,0 +1,61 @@ +#!/bin/sh +TESTDIR="$(readlink -f "$(dirname "$0")")" +. "$TESTDIR/framework" + +setupenvironment +configarchitecture 'i386' 'amd64' + +insertpackage 'unstable' 'available' 'all' '1.0' + +insertinstalledpackage 'manual1' 'i386' '1.0' 'Depends: automatic1' +insertinstalledpackage 'manual2' 'i386' '1.0' + +insertinstalledpackage 'automatic1' 'i386' '1.0' +insertinstalledpackage 'automatic2' 'i386' '1.0' + +insertinstalledpackage 'essential' 'i386' '1.0' 'Essential: yes' +insertinstalledpackage 'conf-only' 'i386' '1.0' '' '' 'deinstall ok config-files' +insertinstalledpackage 'broken' 'i386' '1.0' 'Depends: does-not-exist' + +insertinstalledpackage 'not-obsolete' 'i386' '1.0' +insertpackage 'unstable' 'not-obsolete' 'all' '2.0' + +insertpackage 'unstable' 'foreign' 'amd64' '2.0' + +setupaptarchive + +testsuccess aptmark auto automatic1 automatic2 + +msgmsg "Check that commands understand patterns" + +testfailureequal "E: input:0-14: error: Unrecognized pattern '?not-a-pattern' + ?not-a-pattern + ^^^^^^^^^^^^^^ +N: Unable to locate package ?not-a-pattern +N: Couldn't find any package by glob '?not-a-pattern' +E: Regex compilation error - Invalid preceding regular expression +N: Couldn't find any package by regex '?not-a-pattern' +E: input:0-14: error: Unrecognized pattern '?not-a-pattern' + ?not-a-pattern + ^^^^^^^^^^^^^^ +N: Unable to locate package ?not-a-pattern +N: Couldn't find any package by glob '?not-a-pattern' +E: Regex compilation error - Invalid preceding regular expression +N: Couldn't find any package by regex '?not-a-pattern' +E: No packages found" apt show '?not-a-pattern' + +testfailureequal "Listing... +E: input:0-14: error: Unrecognized pattern '?not-a-pattern' + ?not-a-pattern + ^^^^^^^^^^^^^^" apt list '?not-a-pattern' + +testfailureequal "Reading package lists... +Building dependency tree... +Reading state information... +E: input:0-14: error: Unrecognized pattern '?not-a-pattern' + ?not-a-pattern + ^^^^^^^^^^^^^^ +E: Unable to locate package ?not-a-pattern +E: Couldn't find any package by glob '?not-a-pattern' +E: Regex compilation error - Invalid preceding regular expression +E: Couldn't find any package by regex '?not-a-pattern'" apt install -s '?not-a-pattern' -- cgit v1.2.3-70-g09d2 From c64a85dd7524546864603b955f601bf64c9a4bcf Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Thu, 15 Aug 2019 12:50:22 +0200 Subject: Add patterns for the existing CacheFilter::Matcher classes This implements the basic logic patterns: ?and ?false ?not ?or ?true and the basic package patterns: ?architecture ?name ?x-name-fnmatch --- apt-pkg/cachefilter-patterns.cc | 30 ++++++++++++++++++ doc/apt-patterns.7.xml | 38 +++++++++++++++++++++-- test/integration/test-apt-patterns | 62 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 2 deletions(-) (limited to 'apt-pkg/cachefilter-patterns.cc') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index ea4cf3cd8..dd5d741d3 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -208,6 +208,36 @@ std::unique_ptr PatternParser::aPattern(std::unique_p if (node == nullptr) nodeP->error("Expected a pattern"); + if (node->matches("?architecture", 1, 1)) + return std::make_unique(aWord(node->arguments[0])); + if (node->matches("?false", 0, 0)) + return std::make_unique(); + if (node->matches("?name", 1, 1)) + return std::make_unique(aWord(node->arguments[0])); + if (node->matches("?not", 1, 1)) + return std::make_unique(aPattern(node->arguments[0]).release()); + if (node->matches("?true", 0, 0)) + return std::make_unique(); + if (node->matches("?x-name-fnmatch", 1, 1)) + return std::make_unique(aWord(node->arguments[0])); + + // Variable argument patterns + if (node->matches("?and", 0, -1)) + { + auto pattern = std::make_unique(); + for (auto &arg : node->arguments) + pattern->AND(aPattern(arg).release()); + return pattern; + } + if (node->matches("?or", 0, -1)) + { + auto pattern = std::make_unique(); + + for (auto &arg : node->arguments) + pattern->OR(aPattern(arg).release()); + return pattern; + } + node->error(rstrprintf("Unrecognized pattern '%s'", node->term.to_string().c_str())); return nullptr; diff --git a/doc/apt-patterns.7.xml b/doc/apt-patterns.7.xml index 079c493e4..5aa352f03 100644 --- a/doc/apt-patterns.7.xml +++ b/doc/apt-patterns.7.xml @@ -36,12 +36,46 @@ - Syntax + Logic patterns + + These patterns provide the basic means to combine other patterns into + more complex expressions, as well as ?true and ?false + patterns. + + ?and(PATTERN, PATTERN, ...) + Selects objects where all specified patterns match. + + ?false + Selects nothing. + + ?not(PATTERN) + Selects objects where PATTERN does not match. + + ?or(PATTERN, PATTERN, ...) + Selects objects where at least one of the specified patterns match. + + ?true + Selects all objects. + + + + + Package patterns + + These patterns select specific packages. + + + ?architecture(WILDCARD) + Selects packages matching the specified architecture, which may contain wildcards using any. + + ?name(REGEX) + Selects packages where the name matches the given regular expression. + - + Examples diff --git a/test/integration/test-apt-patterns b/test/integration/test-apt-patterns index c33fa1bf8..0d7b1540d 100755 --- a/test/integration/test-apt-patterns +++ b/test/integration/test-apt-patterns @@ -59,3 +59,65 @@ E: Unable to locate package ?not-a-pattern E: Couldn't find any package by glob '?not-a-pattern' E: Regex compilation error - Invalid preceding regular expression E: Couldn't find any package by regex '?not-a-pattern'" apt install -s '?not-a-pattern' + + +msgmsg "Ensure that argument lists are present where needed, and absent elsewhere" + +testfailureequal "Listing... +E: input:0-7: error: ?true does not expect an argument list + ?true() + ^^^^^^^" apt list '?true()' +testfailureequal "Listing... +E: input:0-4: error: ?and expects an argument list + ?and + ^^^^" apt list '?and' +testfailureequal "Listing... +E: input:0-3: error: ?or expects an argument list + ?or + ^^^" apt list '?or' + + +msgmsg "Basic logic: true, false, not, ?or, ?and" +for pattern in '?true' '?not(?false)'; do +testsuccessequal "Listing... +automatic1/now 1.0 i386 [installed,local] +automatic2/now 1.0 i386 [installed,local] +available/unstable 1.0 all +broken/now 1.0 i386 [installed,local] +conf-only/now 1.0 i386 [residual-config] +dpkg/now 1.16.2+fake all [installed,local] +essential/now 1.0 i386 [installed,local] +foreign/unstable 2.0 amd64 +manual1/now 1.0 i386 [installed,local] +manual2/now 1.0 i386 [installed,local] +not-obsolete/unstable 2.0 i386 [upgradable from: 1.0]" apt list "$pattern" +done +testsuccessequal "Listing..." apt list '?false' +testsuccessequal "Listing..." apt list '?not(?true)' +testsuccessequal "Listing... +automatic1/now 1.0 i386 [installed,local] +automatic2/now 1.0 i386 [installed,local] +manual1/now 1.0 i386 [installed,local] +manual2/now 1.0 i386 [installed,local]" apt list '?or(?name(^automatic),?name(^manual))' +testsuccessequal "Listing... +automatic1/now 1.0 i386 [installed,local]" apt list '?and(?name(^automatic),?name(1$))' + + +msgmsg "Package patterns" + +testsuccessequal "Listing... +foreign/unstable 2.0 amd64" apt list '?architecture(amd64)' + +# XXX FIXME We should have support for foreign and native +testsuccessequal "Listing..." apt list '?architecture(foreign)' +testsuccessequal "Listing..." apt list '?architecture(native)' + + +testsuccessequal "Listing... +automatic1/now 1.0 i386 [installed,local] +automatic2/now 1.0 i386 [installed,local]" apt list '?name(^automatic)' + +testsuccessequal "Listing..." apt list '?x-name-fnmatch(1)' +testsuccessequal "Listing... +automatic1/now 1.0 i386 [installed,local] +manual1/now 1.0 i386 [installed,local]" apt list '?x-name-fnmatch(*1)' -- cgit v1.2.3-70-g09d2 From 083e72a5939d33dc1fd7596aa441a9982332f776 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Thu, 15 Aug 2019 13:12:43 +0200 Subject: Add ?automatic and ?garbage patterns These patterns allow you to identify automatically installed packages, as well as automatically installed packages that are no longer reachable from the manually installed ones. --- apt-pkg/cachefilter-patterns.cc | 4 ++++ apt-pkg/cachefilter-patterns.h | 26 ++++++++++++++++++++++++++ doc/apt-patterns.7.xml | 11 +++++++++++ test/integration/test-apt-patterns | 6 ++++++ 4 files changed, 47 insertions(+) (limited to 'apt-pkg/cachefilter-patterns.cc') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index dd5d741d3..6b506b740 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -210,8 +210,12 @@ std::unique_ptr PatternParser::aPattern(std::unique_p if (node->matches("?architecture", 1, 1)) return std::make_unique(aWord(node->arguments[0])); + if (node->matches("?automatic", 0, 0)) + return std::make_unique(file); if (node->matches("?false", 0, 0)) return std::make_unique(); + if (node->matches("?garbage", 0, 0)) + return std::make_unique(file); if (node->matches("?name", 1, 1)) return std::make_unique(aWord(node->arguments[0])); if (node->matches("?not", 1, 1)) diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h index 3097ba94b..4c2ef48df 100644 --- a/apt-pkg/cachefilter-patterns.h +++ b/apt-pkg/cachefilter-patterns.h @@ -113,6 +113,32 @@ struct PatternParser std::string aWord(std::unique_ptr &nodeP); }; +namespace Patterns +{ +using namespace APT::CacheFilter; + +struct PackageIsAutomatic : public PackageMatcher +{ + pkgCacheFile *Cache; + explicit PackageIsAutomatic(pkgCacheFile *Cache) : Cache(Cache) {} + bool operator()(pkgCache::PkgIterator const &Pkg) override + { + assert(Cache != nullptr); + return ((*Cache)[Pkg].Flags & pkgCache::Flag::Auto) != 0; + } +}; + +struct PackageIsGarbage : public PackageMatcher +{ + pkgCacheFile *Cache; + explicit PackageIsGarbage(pkgCacheFile *Cache) : Cache(Cache) {} + bool operator()(pkgCache::PkgIterator const &Pkg) override + { + assert(Cache != nullptr); + return (*Cache)[Pkg].Garbage; + } +}; +} // namespace Patterns } // namespace Internal } // namespace APT #endif diff --git a/doc/apt-patterns.7.xml b/doc/apt-patterns.7.xml index 5aa352f03..327ea17d5 100644 --- a/doc/apt-patterns.7.xml +++ b/doc/apt-patterns.7.xml @@ -69,6 +69,12 @@ ?architecture(WILDCARD) Selects packages matching the specified architecture, which may contain wildcards using any. + ?automatic + Selects packages that were installed automatically. + + ?garbage + Selects packages that can be removed automatically. + ?name(REGEX) Selects packages where the name matches the given regular expression. @@ -77,6 +83,11 @@ Examples + + apt remove ?garbage + Remove all packages that are automatically installed and no longer needed - same as apt autoremove + + Migrating from aptitude diff --git a/test/integration/test-apt-patterns b/test/integration/test-apt-patterns index 0d7b1540d..9a2c74f83 100755 --- a/test/integration/test-apt-patterns +++ b/test/integration/test-apt-patterns @@ -112,6 +112,12 @@ foreign/unstable 2.0 amd64" apt list '?architecture(amd64)' testsuccessequal "Listing..." apt list '?architecture(foreign)' testsuccessequal "Listing..." apt list '?architecture(native)' +testsuccessequal "Listing... +automatic1/now 1.0 i386 [installed,local] +automatic2/now 1.0 i386 [installed,local]" apt list '?automatic' + +testsuccessequal "Listing... +automatic2/now 1.0 i386 [installed,local]" apt list '?garbage' testsuccessequal "Listing... automatic1/now 1.0 i386 [installed,local] -- cgit v1.2.3-70-g09d2 From 89790c11799c4144e37a6a1f0dbe0f6fff89ea89 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Thu, 15 Aug 2019 13:23:55 +0200 Subject: Add ?obsolete and ?upgradable patterns These match packages that have no version in a repository, or where an upgrade is available. Notably, ?and(?obsolete,?upgradable) == ?false because an upgradable package is by definition not obsolete. --- apt-pkg/cachefilter-patterns.cc | 4 ++++ apt-pkg/cachefilter-patterns.h | 35 +++++++++++++++++++++++++++++++++++ doc/apt-patterns.7.xml | 6 ++++++ test/integration/test-apt-patterns | 10 ++++++++++ 4 files changed, 55 insertions(+) (limited to 'apt-pkg/cachefilter-patterns.cc') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index 6b506b740..b97d65a03 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -220,8 +220,12 @@ std::unique_ptr PatternParser::aPattern(std::unique_p return std::make_unique(aWord(node->arguments[0])); if (node->matches("?not", 1, 1)) return std::make_unique(aPattern(node->arguments[0]).release()); + if (node->matches("?obsolete", 0, 0)) + return std::make_unique(); if (node->matches("?true", 0, 0)) return std::make_unique(); + if (node->matches("?upgradable", 0, 0)) + return std::make_unique(file); if (node->matches("?x-name-fnmatch", 1, 1)) return std::make_unique(aWord(node->arguments[0])); diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h index 4c2ef48df..68ad32f9a 100644 --- a/apt-pkg/cachefilter-patterns.h +++ b/apt-pkg/cachefilter-patterns.h @@ -138,6 +138,41 @@ struct PackageIsGarbage : public PackageMatcher return (*Cache)[Pkg].Garbage; } }; + +struct PackageIsObsolete : public PackageMatcher +{ + bool operator()(pkgCache::PkgIterator const &pkg) override + { + // This code can be written without loops, as aptitude does, but it + // is far less readable. + if (pkg.CurrentVer().end()) + return false; + + // See if there is any version that exists in a repository, + // if so return false + for (auto ver = pkg.VersionList(); !ver.end(); ver++) + { + for (auto file = ver.FileList(); !file.end(); file++) + { + if ((file.File()->Flags & pkgCache::Flag::NotSource) == 0) + return false; + } + } + + return true; + } +}; + +struct PackageIsUpgradable : public PackageMatcher +{ + pkgCacheFile *Cache; + explicit PackageIsUpgradable(pkgCacheFile *Cache) : Cache(Cache) {} + bool operator()(pkgCache::PkgIterator const &Pkg) override + { + assert(Cache != nullptr); + return Pkg->CurrentVer != 0 && (*Cache)[Pkg].Upgradable(); + } +}; } // namespace Patterns } // namespace Internal } // namespace APT diff --git a/doc/apt-patterns.7.xml b/doc/apt-patterns.7.xml index 327ea17d5..6058d7a74 100644 --- a/doc/apt-patterns.7.xml +++ b/doc/apt-patterns.7.xml @@ -78,6 +78,12 @@ ?name(REGEX) Selects packages where the name matches the given regular expression. + ?obsolete + Selects packages that no longer exist in repositories. + + ?upgradable + Selects packages that can be upgraded (have a newer candidate). + diff --git a/test/integration/test-apt-patterns b/test/integration/test-apt-patterns index 9a2c74f83..549089d8a 100755 --- a/test/integration/test-apt-patterns +++ b/test/integration/test-apt-patterns @@ -123,6 +123,16 @@ testsuccessequal "Listing... automatic1/now 1.0 i386 [installed,local] automatic2/now 1.0 i386 [installed,local]" apt list '?name(^automatic)' +testsuccessequal "Listing... +available/unstable 1.0 all +conf-only/now 1.0 i386 [residual-config] +foreign/unstable 2.0 amd64 +not-obsolete/unstable 2.0 i386 [upgradable from: 1.0]" apt list '?not(?obsolete)' + +testsuccessequal "Listing... +not-obsolete/unstable 2.0 i386 [upgradable from: 1.0] +N: There is 1 additional version. Please use the '-a' switch to see it" apt list '?upgradable' + testsuccessequal "Listing..." apt list '?x-name-fnmatch(1)' testsuccessequal "Listing... automatic1/now 1.0 i386 [installed,local] -- cgit v1.2.3-70-g09d2 From a3e9ab661b2d670f7d0da53a5f56c52af0763c5b Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Thu, 15 Aug 2019 13:31:30 +0200 Subject: Add ?config-files and ?installed patterns These two are mutually exclusive states of installed-ness. And ?installed package is fully unpacked and configured; a ?config-files package only has config files left. --- apt-pkg/cachefilter-patterns.cc | 4 ++++ apt-pkg/cachefilter-patterns.h | 19 +++++++++++++++++++ doc/apt-patterns.7.xml | 9 +++++++++ test/integration/test-apt-patterns | 18 ++++++++++++++++++ 4 files changed, 50 insertions(+) (limited to 'apt-pkg/cachefilter-patterns.cc') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index b97d65a03..0f23890b4 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -212,10 +212,14 @@ std::unique_ptr PatternParser::aPattern(std::unique_p return std::make_unique(aWord(node->arguments[0])); if (node->matches("?automatic", 0, 0)) return std::make_unique(file); + if (node->matches("?config-files", 0, 0)) + return std::make_unique(); if (node->matches("?false", 0, 0)) return std::make_unique(); if (node->matches("?garbage", 0, 0)) return std::make_unique(file); + if (node->matches("?installed", 0, 0)) + return std::make_unique(file); if (node->matches("?name", 1, 1)) return std::make_unique(aWord(node->arguments[0])); if (node->matches("?not", 1, 1)) diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h index 68ad32f9a..7770385c9 100644 --- a/apt-pkg/cachefilter-patterns.h +++ b/apt-pkg/cachefilter-patterns.h @@ -128,6 +128,14 @@ struct PackageIsAutomatic : public PackageMatcher } }; +struct PackageIsConfigFiles : public PackageMatcher +{ + bool operator()(pkgCache::PkgIterator const &Pkg) override + { + return Pkg->CurrentState == pkgCache::State::ConfigFiles; + } +}; + struct PackageIsGarbage : public PackageMatcher { pkgCacheFile *Cache; @@ -139,6 +147,17 @@ struct PackageIsGarbage : public PackageMatcher } }; +struct PackageIsInstalled : public PackageMatcher +{ + pkgCacheFile *Cache; + explicit PackageIsInstalled(pkgCacheFile *Cache) : Cache(Cache) {} + bool operator()(pkgCache::PkgIterator const &Pkg) override + { + assert(Cache != nullptr); + return Pkg->CurrentVer != 0; + } +}; + struct PackageIsObsolete : public PackageMatcher { bool operator()(pkgCache::PkgIterator const &pkg) override diff --git a/doc/apt-patterns.7.xml b/doc/apt-patterns.7.xml index 6058d7a74..64dad4dd4 100644 --- a/doc/apt-patterns.7.xml +++ b/doc/apt-patterns.7.xml @@ -72,9 +72,15 @@ ?automatic Selects packages that were installed automatically. + ?config-files + Selects packages that are not fully installed, but have solely residual configuration files left. + ?garbage Selects packages that can be removed automatically. + ?installed + Selects packages that are currently installed. + ?name(REGEX) Selects packages where the name matches the given regular expression. @@ -93,6 +99,9 @@ apt remove ?garbage Remove all packages that are automatically installed and no longer needed - same as apt autoremove + apt purge ?config-files + Purge all packages that only have configuration files left + diff --git a/test/integration/test-apt-patterns b/test/integration/test-apt-patterns index 549089d8a..b84605256 100755 --- a/test/integration/test-apt-patterns +++ b/test/integration/test-apt-patterns @@ -116,9 +116,27 @@ testsuccessequal "Listing... automatic1/now 1.0 i386 [installed,local] automatic2/now 1.0 i386 [installed,local]" apt list '?automatic' +testsuccessequal "Listing... +conf-only/now 1.0 i386 [residual-config]" apt list '?config-files' + testsuccessequal "Listing... automatic2/now 1.0 i386 [installed,local]" apt list '?garbage' +testsuccessequal "Listing... +automatic1/now 1.0 i386 [installed,local] +automatic2/now 1.0 i386 [installed,local] +broken/now 1.0 i386 [installed,local] +dpkg/now 1.16.2+fake all [installed,local] +essential/now 1.0 i386 [installed,local] +manual1/now 1.0 i386 [installed,local] +manual2/now 1.0 i386 [installed,local] +not-obsolete/unstable 2.0 i386 [upgradable from: 1.0]" apt list '?installed' + +testsuccessequal "Listing... +available/unstable 1.0 all +conf-only/now 1.0 i386 [residual-config] +foreign/unstable 2.0 amd64" apt list '?not(?installed)' + testsuccessequal "Listing... automatic1/now 1.0 i386 [installed,local] automatic2/now 1.0 i386 [installed,local]" apt list '?name(^automatic)' -- cgit v1.2.3-70-g09d2 From 9282c9094ab3e77cebe90e97b2f9a5fc21463e2f Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Thu, 15 Aug 2019 13:32:54 +0200 Subject: Add ?broken pattern This matches all packages that have broken dependencies in the installed version or the version selected for install. --- apt-pkg/cachefilter-patterns.cc | 2 ++ apt-pkg/cachefilter-patterns.h | 12 ++++++++++++ doc/apt-patterns.7.xml | 3 +++ test/integration/test-apt-patterns | 3 +++ 4 files changed, 20 insertions(+) (limited to 'apt-pkg/cachefilter-patterns.cc') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index 0f23890b4..a180cf78e 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -212,6 +212,8 @@ std::unique_ptr PatternParser::aPattern(std::unique_p return std::make_unique(aWord(node->arguments[0])); if (node->matches("?automatic", 0, 0)) return std::make_unique(file); + if (node->matches("?broken", 0, 0)) + return std::make_unique(file); if (node->matches("?config-files", 0, 0)) return std::make_unique(); if (node->matches("?false", 0, 0)) diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h index 7770385c9..c219e5bcd 100644 --- a/apt-pkg/cachefilter-patterns.h +++ b/apt-pkg/cachefilter-patterns.h @@ -128,6 +128,18 @@ struct PackageIsAutomatic : public PackageMatcher } }; +struct PackageIsBroken : public PackageMatcher +{ + pkgCacheFile *Cache; + explicit PackageIsBroken(pkgCacheFile *Cache) : Cache(Cache) {} + bool operator()(pkgCache::PkgIterator const &Pkg) override + { + assert(Cache != nullptr); + auto state = (*Cache)[Pkg]; + return state.InstBroken() || state.NowBroken(); + } +}; + struct PackageIsConfigFiles : public PackageMatcher { bool operator()(pkgCache::PkgIterator const &Pkg) override diff --git a/doc/apt-patterns.7.xml b/doc/apt-patterns.7.xml index 64dad4dd4..e1c64de8e 100644 --- a/doc/apt-patterns.7.xml +++ b/doc/apt-patterns.7.xml @@ -72,6 +72,9 @@ ?automatic Selects packages that were installed automatically. + ?broken + Selects packages that have broken dependencies. + ?config-files Selects packages that are not fully installed, but have solely residual configuration files left. diff --git a/test/integration/test-apt-patterns b/test/integration/test-apt-patterns index b84605256..e566c6bd2 100755 --- a/test/integration/test-apt-patterns +++ b/test/integration/test-apt-patterns @@ -116,6 +116,9 @@ testsuccessequal "Listing... automatic1/now 1.0 i386 [installed,local] automatic2/now 1.0 i386 [installed,local]" apt list '?automatic' +testsuccessequal "Listing... +broken/now 1.0 i386 [installed,local]" apt list '?broken' + testsuccessequal "Listing... conf-only/now 1.0 i386 [residual-config]" apt list '?config-files' -- cgit v1.2.3-70-g09d2 From 08762e0e4e2923360339eeb4d8ed26a00d7f1de5 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Thu, 15 Aug 2019 13:35:01 +0200 Subject: Add ?essential pattern This matches all packages where at least one of the versions is marked essential; or well, whenver apt considers a package essential. --- apt-pkg/cachefilter-patterns.cc | 2 ++ apt-pkg/cachefilter-patterns.h | 7 +++++++ doc/apt-patterns.7.xml | 3 +++ test/integration/test-apt-patterns | 3 +++ 4 files changed, 15 insertions(+) (limited to 'apt-pkg/cachefilter-patterns.cc') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index a180cf78e..ea35b9c46 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -216,6 +216,8 @@ std::unique_ptr PatternParser::aPattern(std::unique_p return std::make_unique(file); if (node->matches("?config-files", 0, 0)) return std::make_unique(); + if (node->matches("?essential", 0, 0)) + return std::make_unique(); if (node->matches("?false", 0, 0)) return std::make_unique(); if (node->matches("?garbage", 0, 0)) diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h index c219e5bcd..17f1fc752 100644 --- a/apt-pkg/cachefilter-patterns.h +++ b/apt-pkg/cachefilter-patterns.h @@ -158,6 +158,13 @@ struct PackageIsGarbage : public PackageMatcher return (*Cache)[Pkg].Garbage; } }; +struct PackageIsEssential : public PackageMatcher +{ + bool operator()(pkgCache::PkgIterator const &Pkg) override + { + return (Pkg->Flags & pkgCache::Flag::Essential) != 0; + } +}; struct PackageIsInstalled : public PackageMatcher { diff --git a/doc/apt-patterns.7.xml b/doc/apt-patterns.7.xml index e1c64de8e..11031be95 100644 --- a/doc/apt-patterns.7.xml +++ b/doc/apt-patterns.7.xml @@ -78,6 +78,9 @@ ?config-files Selects packages that are not fully installed, but have solely residual configuration files left. + ?essential + Selects packages that have Essential: yes set in their control file. + ?garbage Selects packages that can be removed automatically. diff --git a/test/integration/test-apt-patterns b/test/integration/test-apt-patterns index e566c6bd2..1efb10d2d 100755 --- a/test/integration/test-apt-patterns +++ b/test/integration/test-apt-patterns @@ -122,6 +122,9 @@ broken/now 1.0 i386 [installed,local]" apt list '?broken' testsuccessequal "Listing... conf-only/now 1.0 i386 [residual-config]" apt list '?config-files' +testsuccessequal "Listing... +essential/now 1.0 i386 [installed,local]" apt list '?essential' + testsuccessequal "Listing... automatic2/now 1.0 i386 [installed,local]" apt list '?garbage' -- cgit v1.2.3-70-g09d2 From af674d82d5cd36e22223ec49675d32adad07e0a9 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Thu, 15 Aug 2019 13:36:04 +0200 Subject: Add the ?exact-name pattern The ?exact-name pattern matches the name exactly, there is no substring matching going on, or any regular expression or fnmatch magic. --- apt-pkg/cachefilter-patterns.cc | 2 ++ apt-pkg/cachefilter-patterns.h | 10 ++++++++++ doc/apt-patterns.7.xml | 3 +++ test/integration/test-apt-patterns | 4 ++++ 4 files changed, 19 insertions(+) (limited to 'apt-pkg/cachefilter-patterns.cc') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index ea35b9c46..f078924ff 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -218,6 +218,8 @@ std::unique_ptr PatternParser::aPattern(std::unique_p return std::make_unique(); if (node->matches("?essential", 0, 0)) return std::make_unique(); + if (node->matches("?exact-name", 1, 1)) + return std::make_unique(aWord(node->arguments[0])); if (node->matches("?false", 0, 0)) return std::make_unique(); if (node->matches("?garbage", 0, 0)) diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h index 17f1fc752..993839425 100644 --- a/apt-pkg/cachefilter-patterns.h +++ b/apt-pkg/cachefilter-patterns.h @@ -166,6 +166,16 @@ struct PackageIsEssential : public PackageMatcher } }; +struct PackageHasExactName : public PackageMatcher +{ + std::string name; + explicit PackageHasExactName(std::string name) : name(name) {} + bool operator()(pkgCache::PkgIterator const &Pkg) override + { + return Pkg.Name() == name; + } +}; + struct PackageIsInstalled : public PackageMatcher { pkgCacheFile *Cache; diff --git a/doc/apt-patterns.7.xml b/doc/apt-patterns.7.xml index 11031be95..2eda77b55 100644 --- a/doc/apt-patterns.7.xml +++ b/doc/apt-patterns.7.xml @@ -81,6 +81,9 @@ ?essential Selects packages that have Essential: yes set in their control file. + ?exact-name(NAME) + Selects packages with the exact specified name. + ?garbage Selects packages that can be removed automatically. diff --git a/test/integration/test-apt-patterns b/test/integration/test-apt-patterns index 1efb10d2d..c75793e55 100755 --- a/test/integration/test-apt-patterns +++ b/test/integration/test-apt-patterns @@ -125,6 +125,10 @@ conf-only/now 1.0 i386 [residual-config]" apt list '?config-files' testsuccessequal "Listing... essential/now 1.0 i386 [installed,local]" apt list '?essential' +testsuccessequal "Listing..." apt list '?exact-name(automatic)' +testsuccessequal "Listing... +automatic1/now 1.0 i386 [installed,local]" apt list '?exact-name(automatic1)' + testsuccessequal "Listing... automatic2/now 1.0 i386 [installed,local]" apt list '?garbage' -- cgit v1.2.3-70-g09d2 From c94b507b92b3d833761f3e416d4d5bba709bf87f Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Thu, 15 Aug 2019 13:40:31 +0200 Subject: Add ?virtual pattern This matches any package that does not have versions. --- apt-pkg/cachefilter-patterns.cc | 2 ++ apt-pkg/cachefilter-patterns.h | 8 ++++++++ doc/apt-patterns.7.xml | 5 +++++ test/integration/test-apt-patterns | 6 ++++++ 4 files changed, 21 insertions(+) (limited to 'apt-pkg/cachefilter-patterns.cc') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index f078924ff..986faaf52 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -236,6 +236,8 @@ std::unique_ptr PatternParser::aPattern(std::unique_p return std::make_unique(); if (node->matches("?upgradable", 0, 0)) return std::make_unique(file); + if (node->matches("?virtual", 0, 0)) + return std::make_unique(); if (node->matches("?x-name-fnmatch", 1, 1)) return std::make_unique(aWord(node->arguments[0])); diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h index 993839425..d37da815f 100644 --- a/apt-pkg/cachefilter-patterns.h +++ b/apt-pkg/cachefilter-patterns.h @@ -221,6 +221,14 @@ struct PackageIsUpgradable : public PackageMatcher return Pkg->CurrentVer != 0 && (*Cache)[Pkg].Upgradable(); } }; + +struct PackageIsVirtual : public PackageMatcher +{ + bool operator()(pkgCache::PkgIterator const &Pkg) override + { + return Pkg->VersionList == 0; + } +}; } // namespace Patterns } // namespace Internal } // namespace APT diff --git a/doc/apt-patterns.7.xml b/doc/apt-patterns.7.xml index 2eda77b55..efd4293dc 100644 --- a/doc/apt-patterns.7.xml +++ b/doc/apt-patterns.7.xml @@ -99,6 +99,11 @@ ?upgradable Selects packages that can be upgraded (have a newer candidate). + ?virtual + 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. + diff --git a/test/integration/test-apt-patterns b/test/integration/test-apt-patterns index c75793e55..92c76edd1 100755 --- a/test/integration/test-apt-patterns +++ b/test/integration/test-apt-patterns @@ -161,6 +161,12 @@ testsuccessequal "Listing... not-obsolete/unstable 2.0 i386 [upgradable from: 1.0] N: There is 1 additional version. Please use the '-a' switch to see it" apt list '?upgradable' +testsuccessequal "Package: does-not-exist +State: not a real package (virtual) +N: Can't select candidate version from package does-not-exist as it has no candidate +N: Can't select versions from package 'does-not-exist' as it is purely virtual +N: No packages found" apt show '?virtual' + testsuccessequal "Listing..." apt list '?x-name-fnmatch(1)' testsuccessequal "Listing... automatic1/now 1.0 i386 [installed,local] -- cgit v1.2.3-70-g09d2 From d64f0f343d2fcfe1d7768fa9acad83e34fa519f2 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Thu, 15 Aug 2019 15:05:52 +0200 Subject: patterns: Improve error reporting at end of argument list --- apt-pkg/cachefilter-patterns.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'apt-pkg/cachefilter-patterns.cc') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index 986faaf52..1f448dae9 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -106,8 +106,10 @@ std::unique_ptr PatternTreeParser::parsePattern() skipSpace(); } + node->end = state.offset; if (sentence[state.offset] != ')') - throw Error{*node, rstrprintf("Expected closing parenthesis, received %d", sentence[state.offset])}; + throw Error{node->arguments.empty() ? *node : *node->arguments[node->arguments.size() - 1], + rstrprintf("Expected closing parenthesis or comma after last argument, received %c", sentence[state.offset])}; node->end = ++state.offset; return node; -- cgit v1.2.3-70-g09d2 From d18b6095862e8268b4d2cd8c0b3140829a1e4950 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Thu, 15 Aug 2019 15:06:20 +0200 Subject: patterns: Allow more complex words Only disallow ,() and on the start of a word also ~ and ?. Make sure to include \0 as disallowed. --- apt-pkg/cachefilter-patterns.cc | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'apt-pkg/cachefilter-patterns.cc') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index 1f448dae9..bf6166ee4 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -144,17 +144,15 @@ std::unique_ptr PatternTreeParser::parseQuotedWord() // Parse a bare word atom std::unique_ptr PatternTreeParser::parseWord() { - static const APT::StringView CHARS("0123456789" - "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "-.*^$[]_\\"); - if (CHARS.find(sentence[state.offset]) == APT::StringView::npos) + static const APT::StringView DISALLOWED_START("?~,()\0", 6); + static const APT::StringView DISALLOWED(",()\0", 4); + if (DISALLOWED_START.find(sentence[state.offset]) != APT::StringView::npos) return nullptr; auto node = std::make_unique(); node->start = state.offset; - while (CHARS.find(sentence[state.offset]) != APT::StringView::npos) + while (DISALLOWED.find(sentence[state.offset]) == APT::StringView::npos) state.offset++; node->end = state.offset; -- cgit v1.2.3-70-g09d2