From: Michael Andres Date: Mon, 20 Apr 2009 14:47:57 +0000 (+0200) Subject: New classes wraping satsolver datamatcher (Match and sat::AttrMatcher) X-Git-Tag: 6.6.0~32 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=2b060281952c78ce3b4990794819f03fe0f5b6af;p=platform%2Fupstream%2Flibzypp.git New classes wraping satsolver datamatcher (Match and sat::AttrMatcher) - zypp::Match - string matching mode and option bit flags - zypp::sat::AttrMatcher - string matching functor supporting STRING|SUBSTRING|GLOB|REGEX --- diff --git a/tests/sat/AttrMatcher_test.cc b/tests/sat/AttrMatcher_test.cc new file mode 100644 index 0000000..05e0d88 --- /dev/null +++ b/tests/sat/AttrMatcher_test.cc @@ -0,0 +1,131 @@ +#include "TestSetup.h" +#include +#include +#include + +/////////////////////////////////////////////////////////////////// +// +// CLASS NAME : Matcher +// +/////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE(Match_default) +{ + Match m; + BOOST_CHECK( !m ); // eval in boolean context + BOOST_CHECK_EQUAL( m, Match::NOTHING ); + BOOST_CHECK_EQUAL( m.get(), 0 ); + + // set the mode part + BOOST_CHECK_EQUAL( m |= Match::STRING, Match::STRING ); + + m.setModeSubstring(); + BOOST_CHECK_EQUAL( m, Match::SUBSTRING ); + + m.setMode( Match::GLOB ); + BOOST_CHECK_EQUAL( m, Match::GLOB ); + + BOOST_CHECK_EQUAL( m = Match::REGEX, Match::REGEX ); + + BOOST_CHECK( m.isModeRegex() ); + m |= Match::NOCASE | Match::FILES; + BOOST_CHECK_EQUAL( m, Match::REGEX | Match::NOCASE | Match::FILES ); + + BOOST_CHECK( m.testAnyOf( Match::SUBSTRING | Match::NOCASE | Match::FILES ) ); + BOOST_CHECK( !m.test( Match::SUBSTRING | Match::NOCASE | Match::FILES ) ); + BOOST_CHECK( m.test( Match::REGEX | Match::NOCASE | Match::FILES ) ); + BOOST_CHECK( m.test( Match::NOCASE | Match::FILES ) ); + BOOST_CHECK( m != (Match::NOCASE | Match::FILES) ); + BOOST_CHECK_EQUAL( m.flags(),Match::NOCASE | Match::FILES ); + + m -= Match::NOCASE; // remove flags + BOOST_CHECK( m.test( Match::REGEX | Match::FILES ) ); + m -= Match::REGEX; + BOOST_CHECK_EQUAL( m, Match::FILES ); +} + +BOOST_AUTO_TEST_CASE(Match_operator) +{ + // Test whether implicit conversions from enum Match::Mode to + // Matcher work. There must be no difference in using mode and flag + // constants. These tests usually fail at compiletime, if some operator + // overload is missing. + // + // E.G.: + // inline Match operator|( const Match & lhs, const Match & rhs ) + // this does not cover (REGEX|SUBSTRING), because if both arguments + // are enum Mode the compiler might want to use operator|(int,int) + // instead. + + Match m( Match::GLOB ); + m = Match::GLOB; + + m |= Match::GLOB; + m = Match::SUBSTRING | Match::GLOB; + + m -= Match::GLOB; + m = Match::SUBSTRING - Match::GLOB; +} + +/////////////////////////////////////////////////////////////////// +// +// CLASS NAME : AttrMatcher +// +/////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE(AttrMatcher_defaultconstructed) +{ + sat::AttrMatcher m; + BOOST_CHECK( !m ); // eval in boolean context + BOOST_CHECK( m.searchstring().empty() ); + BOOST_CHECK_EQUAL( m.flags(), Match() ); + // matches nothing: + BOOST_CHECK( !m( "" ) ); + BOOST_CHECK( !m( " " ) ); + BOOST_CHECK( !m( "a" ) ); + BOOST_CHECK( !m( "default" ) ); + + m.setSearchstring( "fau" ); + BOOST_CHECK( m ); // eval in boolean context +} + +BOOST_AUTO_TEST_CASE(AttrMatcher_STRING) +{ + sat::AttrMatcher m( "fau" ); + BOOST_CHECK_EQUAL( m.flags(), Match::STRING ); + BOOST_CHECK( !m( "" ) ); + BOOST_CHECK( !m( "a" ) ); + BOOST_CHECK( m( "fau" ) ); + BOOST_CHECK( !m( "default" ) ); +} + +BOOST_AUTO_TEST_CASE(AttrMatcher_REGEX) +{ + sat::AttrMatcher m( "fau" ); + + BOOST_CHECK( !m.isCompiled() ); + BOOST_CHECK_NO_THROW( m.compile() ); + + m.setSearchstring( "wa[" ); + BOOST_CHECK( !m.isCompiled() ); + m.setFlags( Match::REGEX ); + BOOST_CHECK( !m.isCompiled() ); + BOOST_CHECK_THROW( m.compile(), MatchInvalidRegexException ); + BOOST_CHECK( !m.isCompiled() ); + + m.setSearchstring( "wa[a]" ); + BOOST_CHECK_NO_THROW( m.compile() ); + BOOST_CHECK( m.isCompiled() ); + + BOOST_CHECK( !m( "was" ) ); + BOOST_CHECK( !m( "qwasq" ) ); + BOOST_CHECK( m( "qwaaq" ) ); +} + +#if 0 +BOOST_AUTO_TEST_CASE(AttrMatcher_) +{ + base::LogControl::TmpLineWriter shutUp( new log::FileLineWriter( "/tmp/YLOG" ) ); + MIL << "GO" << endl; +} +#endif diff --git a/tests/sat/CMakeLists.txt b/tests/sat/CMakeLists.txt index 60775d4..1e34860 100644 --- a/tests/sat/CMakeLists.txt +++ b/tests/sat/CMakeLists.txt @@ -2,6 +2,6 @@ # to find the KeyRingTest receiver INCLUDE_DIRECTORIES( ${LIBZYPP_SOURCE_DIR}/tests/zypp ) -ADD_TESTS(Solvable SolvParsing WhatProvides WhatObsoletes LookupAttr) +ADD_TESTS(Solvable SolvParsing WhatProvides WhatObsoletes LookupAttr AttrMatcher) diff --git a/zypp/CMakeLists.txt b/zypp/CMakeLists.txt index 68c0102..addd92d 100644 --- a/zypp/CMakeLists.txt +++ b/zypp/CMakeLists.txt @@ -508,6 +508,7 @@ SET( zypp_sat_SRCS sat/LocaleSupport.cc sat/LookupAttr.cc sat/SolvAttr.cc + sat/AttrMatcher.cc ) SET( zypp_sat_HEADERS @@ -521,6 +522,7 @@ SET( zypp_sat_HEADERS sat/LookupAttr.h sat/LookupAttrTools.h sat/SolvAttr.h + sat/AttrMatcher.h ) INSTALL( FILES diff --git a/zypp/sat/AttrMatcher.cc b/zypp/sat/AttrMatcher.cc new file mode 100644 index 0000000..026f2ba --- /dev/null +++ b/zypp/sat/AttrMatcher.cc @@ -0,0 +1,318 @@ +/*---------------------------------------------------------------------\ +| ____ _ __ __ ___ | +| |__ / \ / / . \ . \ | +| / / \ V /| _/ _/ | +| / /__ | | | | | | | +| /_____||_| |_| |_| | +| | +\---------------------------------------------------------------------*/ +/** \file zypp/sat/AttrMatcher.cc + * +*/ +extern "C" +{ +#include "satsolver/repo.h" +} + +#include +#include +#include + +#include "zypp/base/LogTools.h" +#include "zypp/base/Gettext.h" +#include "zypp/base/String.h" + +#include "zypp/sat/AttrMatcher.h" + +using std::endl; + +/////////////////////////////////////////////////////////////////// +namespace zypp +{ ///////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////// + // + // CLASS NAME : Match + // + /////////////////////////////////////////////////////////////////// + + const int Match::_modemask = SEARCH_STRINGMASK; + const int Match::_flagmask = ~_modemask; + + // option flags + const Match Match::NOCASE (SEARCH_NOCASE); + const Match Match::NO_STORAGE_SOLVABLE(SEARCH_NO_STORAGE_SOLVABLE); + const Match Match::SUB (SEARCH_SUB); + const Match Match::ARRAYSENTINEL (SEARCH_ARRAYSENTINEL); + const Match Match::SKIP_KIND (SEARCH_SKIP_KIND); + const Match Match::FILES (SEARCH_FILES); + + Match::Mode Match::mode() const + { + switch ( modeval() ) + { + case 0: return NOTHING; break; + case SEARCH_STRING: return STRING; break; + case SEARCH_SUBSTRING: return SUBSTRING; break; + case SEARCH_GLOB: return GLOB; break; + case SEARCH_REGEX: return REGEX; break; + } + return OTHER; + } + + int Match::modeval( Mode mode_r ) + { + switch ( mode_r ) + { + case NOTHING: return 0; break; + case STRING: return SEARCH_STRING; break; + case SUBSTRING: return SEARCH_SUBSTRING; break; + case GLOB: return SEARCH_GLOB; break; + case REGEX: return SEARCH_REGEX; break; + case OTHER: return SEARCH_STRINGMASK; break; + } + return SEARCH_STRINGMASK; + } + + std::string Match::asString() const + { std::ostringstream str; str << *this; return str.str(); } + + std::ostream & operator<<( std::ostream & str, Match::Mode obj ) + { + switch ( obj ) + { +#define OUTS(V) case Match::V: return str << #V; break + OUTS( NOTHING ); + OUTS( STRING ); + OUTS( SUBSTRING ); + OUTS( GLOB ); + OUTS( REGEX ); + OUTS( OTHER ); +#undef OUTS + } + return str << "Match::Mode::UNKNOWN"; + } + + std::ostream & operator<<( std::ostream & str, const Match & obj ) + { + if ( ! obj ) + return str << "NOTHING"; + + const char * sep = "|"; + + int val = obj.modeval(); + switch ( val ) + { + case 0: sep = 0; break; + case SEARCH_STRING: str << "STRING"; break; + case SEARCH_SUBSTRING: str << "SUBSTRING"; break; + case SEARCH_GLOB: str << "GLOB"; break; + case SEARCH_REGEX: str << "REGEX"; break; + default: + str << "OTHER("< _matcher; + + private: + friend Impl * rwcowClone( const Impl * rhs ); + /** clone for RWCOW_pointer */ + Impl * clone() const + { return new Impl( _search, _flags ); } + }; + /////////////////////////////////////////////////////////////////// + + /** \relates AttrMatcher::Impl Stream output */ + inline std::ostream & operator<<( std::ostream & str, const AttrMatcher::Impl & obj ) + { + return str << "\"" << obj.searchstring() << "\"{" << obj.flags() << "}"; + } + + /////////////////////////////////////////////////////////////////// + // + // CLASS NAME : AttrMatcher + // + /////////////////////////////////////////////////////////////////// + + AttrMatcher::AttrMatcher() + : _pimpl( new Impl ) + {} + + AttrMatcher::AttrMatcher( const std::string & search_r ) + : _pimpl( new Impl( search_r, Match::STRING ) ) + {} + + AttrMatcher::AttrMatcher( const std::string & search_r, const Match & flags_r ) + : _pimpl( new Impl( search_r, flags_r ) ) + {} + + AttrMatcher::AttrMatcher( const std::string & search_r, int flags_r ) + : _pimpl( new Impl( search_r, Match(flags_r) ) ) + {} + + void AttrMatcher::compile() const + { return _pimpl->compile(); } + + bool AttrMatcher::isCompiled() const + { return _pimpl->isCompiled(); } + + bool AttrMatcher::doMatch( const char * string_r ) const + { return _pimpl->doMatch( string_r ); } + + const std::string & AttrMatcher::searchstring() const + { return _pimpl->searchstring(); } + + void AttrMatcher::setSearchstring( const std::string & string_r ) + { _pimpl->setSearchstring( string_r ); } + + void AttrMatcher::setSearchstring( const std::string & string_r, const Match & flags_r ) + { + _pimpl->setSearchstring( string_r ); + _pimpl->setFlags( flags_r ); + } + + const Match & AttrMatcher::flags() const + { return _pimpl->flags(); } + + void AttrMatcher::setFlags( const Match & flags_r ) + { _pimpl->setFlags( flags_r ); } + + /****************************************************************** + ** + ** FUNCTION NAME : operator<< + ** FUNCTION TYPE : std::ostream & + */ + std::ostream & operator<<( std::ostream & str, const AttrMatcher & obj ) + { + return str << *obj._pimpl; + } + + ///////////////////////////////////////////////////////////////// + } // namespace sat + /////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////// +} // namespace zypp +/////////////////////////////////////////////////////////////////// diff --git a/zypp/sat/AttrMatcher.h b/zypp/sat/AttrMatcher.h new file mode 100644 index 0000000..155733a --- /dev/null +++ b/zypp/sat/AttrMatcher.h @@ -0,0 +1,396 @@ +/*---------------------------------------------------------------------\ +| ____ _ __ __ ___ | +| |__ / \ / / . \ . \ | +| / / \ V /| _/ _/ | +| / /__ | | | | | | | +| /_____||_| |_| |_| | +| | +\---------------------------------------------------------------------*/ +/** \file zypp/sat/AttrMatcher.h + * +*/ +#ifndef ZYPP_SAT_ATTRMATCHER_H +#define ZYPP_SAT_ATTRMATCHER_H + +extern "C" +{ +struct _Datamatcher; +} + +#include +#include + +#include "zypp/base/PtrTypes.h" +#include "zypp/base/SafeBool.h" +#include "zypp/base/Exception.h" + +/////////////////////////////////////////////////////////////////// +namespace zypp +{ ///////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////// + // + // CLASS NAME : Match + // + /** String matching option flags as used e.g. by \ref sat::AttrMatcher. + * + * + * + * \code + * Match mode( Match::GLOB | Match::NOCASE ); + * \endcode + */ + class Match : private base::SafeBool + { + private: + static const int _modemask; + static const int _flagmask; + + public: + /** Mode flags (mutual exclusive). */ + enum Mode + { + NOTHING, //!< Match nothing + STRING, //!< Excat matching + SUBSTRING, //!< Match substring + GLOB, //!< Glob + REGEX, //!< Regular Expression + OTHER //!< Something else. + }; + + /** \name Option flags + * Some flags are actually \ref sat::LookupAttr specific, as they tell + * how to retrieve the attribute values. The plain \ref sat::AttrMatcher + * will ignore those flags and use the ones related to string matching only + * (like \ref NOCASE). + */ + //@{ + /** If set, match case insensitive. */ + static const Match NOCASE; + /** internal */ + static const Match NO_STORAGE_SOLVABLE; + /** internal */ + static const Match SUB; + /** internal */ + static const Match ARRAYSENTINEL; + /** If set, skip any \c kind: prefix when looking at a \ref Solvable name. */ + static const Match SKIP_KIND; + /** If set, match full path when matching in filelists, otherwise just the basenames. */ + static const Match FILES; + //@} + + public: + /** Default ctor \c 0 or \ref NOTHING. */ + Match() + : _val( 0 ) + {} + + /** Ctor from \ref Mode value. */ + Match( Mode val_r ) + : _val( modeval( val_r ) ) + {} + + /** Just in case one needs it. */ + explicit Match( int val_r ) + : _val( val_r ) + {} + +#ifndef SWIG // Swig treats it as syntax error + /** Evaluate in a boolean context ( != 0 ). */ + using base::SafeBool::operator bool_type; +#endif + + public: + /** Test whether \c all of the \a rhs bits are set (same mode if \a rhs has one). */ + bool test( const Match & rhs ) const + { return ( ( flagval() & rhs.flagval() ) == rhs.flagval() ) + && ( !rhs.modeval() || rhs.modeval() == modeval() ); } + + /** Whether at least one of the \a rhs bits is set (or the same mode). */ + bool testAnyOf( const Match & rhs ) const + { return ( flagval() & rhs.flagval() ) + || ( rhs.modeval() && rhs.modeval() == modeval() ); } + + /** Set all of the \a rhs bits (setting a new mode if \a rhs has one). */ + void set( const Match & rhs ) + { + if ( rhs.modeval() ) + _val = rhs._val | flagval(); // also set the rhs mode + else + _val |= rhs._val; // just set the flags + } + + /** Unset all of the \a rhs bits (unsets mode if the same as \a rhs). */ + void unset( const Match & rhs ) + { + if ( modeval() == rhs.modeval() ) + _val = flagval() & ~rhs.flagval(); // also unset mode + else + _val &= ~rhs.flagval(); // just unset falgs + } + + /** Depending on the value of \a onoff, set or unset flags. */ + void turn( const Match & rhs, bool onoff ) + { onoff ? set( rhs ) : unset( rhs ); } + + /** Add flags. */ + Match & operator|=( const Match & rhs ) + { set( rhs ); return *this; } + + /** Remove flags.*/ + Match & operator-=( const Match & rhs ) + { unset( rhs ); return *this; } + + public: + /** Return the \c mode part. */ + Mode mode() const; + + /** Return the \c flags part. */ + Match flags() const + { return Match( flagval() ); } + + public: + /** \name Low level integer representation. */ + //@{ + /** Return the integer representation. */ + int get() const { return _val; } + /** Return the modes integer representation. */ + int modeval() const { return _val & _modemask; } + /** Return the flags integer representation. */ + int flagval() const { return _val & _flagmask; } + //@} + + public: + /** \name Mode flag manip/query convenience. */ + //@{ + /** Whether this has mode \a rhs */ + bool isMode( Mode rhs ) const + { return modeval() == modeval( rhs ); } + /** Whether this has mode \ref STRING. */ + bool isModeString() const + { return isMode( STRING ); } + /** Whether this has mode \ref SUBSTRING. */ + bool isModeSubstring() const + { return isMode( SUBSTRING ); } + /** Whether this has mode \ref GLOB. */ + bool isModeGlob() const + { return isMode( GLOB ); } + /** Whether this has mode \ref REGEX. */ + bool isModeRegex() const + { return isMode( REGEX ); } + + /** Set the mode part to \a rhs . */ + void setMode( Mode rhs ) + { _val = modeval( rhs ) | flagval(); } + /** Set the mode \ref STRING. */ + void setModeString() + { setMode( STRING ); } + /** Set the mode \ref SUBSTRING. */ + void setModeSubstring() + { setMode( SUBSTRING ); } + /** Set the mode \ref GLOB. */ + void setModeGlob() + { setMode( GLOB ); } + /** Set the mode \ref REGEX. */ + void setModeRegex() + { setMode( REGEX ); } + //@} + + /** String representation. */ + std::string asString() const; + + private: + friend base::SafeBool::operator bool_type() const; + bool boolTest() const { return _val; } + + /** Numeric value for enum (short for Match(m).get()). */ + static int modeval( Mode mode_r ); + + private: + int _val; + }; + + /** \relates Match */ + inline bool operator==( const Match & lhs, const Match & rhs ) + { return lhs.get() == rhs.get(); } + /** \relates Match */ + inline bool operator!=( const Match & lhs, const Match & rhs ) + { return lhs.get() != rhs.get(); } + + /** \relates Match */ + inline Match operator|( const Match & lhs, const Match & rhs ) + { return Match(lhs) |= rhs; } + /** \relates Match \overload to disambiguate 'int|int'. */ + inline Match operator|( Match::Mode lhs, Match::Mode rhs ) + { return Match(lhs) |= rhs; } + + /** \relates Match */ + inline Match operator-( const Match & lhs, const Match & rhs ) + { return Match(lhs) -= rhs; } + /** \relates Match \overload to disambiguate 'int-int'. */ + inline Match operator-( Match::Mode lhs, Match::Mode rhs ) + { return Match(lhs) -= rhs; } + + /** \relates Match::Mode Stream output */ + std::ostream & operator<<( std::ostream & str, Match::Mode obj ); + + /** \relates Match Stream output */ + std::ostream & operator<<( std::ostream & str, const Match & obj ); + + + /////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////// + // + // CLASS NAME : MatchException + // + /** Exceptions thrown from attribute matching. */ + struct MatchException : public Exception + { + /** Supplied message. */ + explicit MatchException( const std::string & msg_r ) : Exception( msg_r ) {} + }; + + /** Unknown match mode. */ + struct MatchUnknownModeException : public MatchException + { + /** Supplied message. */ + explicit MatchUnknownModeException( const std::string & msg_r ) : MatchException( msg_r ) {} + + /** Build message including the \a mode and optional the pattern string. */ + MatchUnknownModeException( const Match & mode_r, const std::string & msg_r = std::string() ); + }; + + /** Invalid regular expression (failed ::regcomp). */ + struct MatchInvalidRegexException : public MatchException + { + /** Supplied message. */ + explicit MatchInvalidRegexException( const std::string & msg_r ) : MatchException( msg_r ) {} + + /** Build message including the \a regex and \c ::regcomp returncode (use \c 0 if unknown). */ + MatchInvalidRegexException( const std::string & regex_r, int regcomp_r ); + }; + + /////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////// + namespace sat + { ///////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////// + // + // CLASS NAME : AttrMatcher + // + /** String matching (STRING|SUBSTRING|GLOB|REGEX). + * + * Used by e.g. \ref PoolQuery and \ref LookupAttr for queries, + * but it can also be used for matching arbitrary strings. + * + * \code + * AttrMatcher matches( "foo" ); + * for_( it, stringlist.begin(), stringlist().end() ) + * { + * if ( matches( *it ) ) + * cout << *it << " has substring 'foo'" << endl; + * } + * \endcode + * + * \Note Those flags are always set: REG_EXTENDED | REG_NOSUB | REG_NEWLINE + */ + class AttrMatcher : private base::SafeBool + { + friend std::ostream & operator<<( std::ostream & str, const AttrMatcher & obj ); + + public: + typedef MatchException Exception; + + public: + /** Implementation */ + class Impl; + + public: + /** Default ctor matches nothing. */ + AttrMatcher(); + + /** Ctor from string matches in \ref Match::STRING mode per default. */ + AttrMatcher( const std::string & search_r ); + + /** Ctor taking string and \ref Match flags. */ + AttrMatcher( const std::string & search_r, const Match & flags_r ); + + /** Low level interface wraps \a flags into \ref Match. */ + AttrMatcher( const std::string & search_r, int flags_r ); + +#ifndef SWIG // Swig treats it as syntax error + /** Evaluate in a boolean context ( ! searchstring().empty() ). */ + using base::SafeBool::operator bool_type; +#endif + + public: + /** Return whether string matches. + * You can use it with any class that impements \c c_str. + * (\c std::string, \ref Pathname, \ref IdString, ...). + * \Note \c NULL never matches. + */ + template + bool operator()( const _Tp & string_r ) const + { return doMatch( string_r.c_str() ); } + /** \overload */ + bool operator()( const char * string_r ) const + { return doMatch( string_r ); } + + public: + /** The current searchstring. */ + const std::string & searchstring() const; + + /** Set a new searchstring. */ + void setSearchstring( const std::string & string_r ); + + /** Set a new searchstring and flags. */ + void setSearchstring( const std::string & string_r, const Match & flags_r ); + + /** The current search flags. */ + const Match & flags() const; + + /** Set new search flags. */ + void setFlags( const Match & flags_r ); + + public: + /** Compile the pattern e.g. in case of \c REGEX. + * \throws MatchUnknownModeException If the \ref Match flag more than + * one mode bit set. + * \throws MatchInvalidRegexException If \ref Match::REGEX is set + * and \ref searchstring is not a valid regular expression. + */ + void compile() const; + + /** Whether the \ref AttrMatcher is already compiled. */ + bool isCompiled() const; + + /** Return whether string matches. + * Compiles the \ref AttrMatcher if this was not yet done. + * \throws MatchException Any of the exceptions thrown by \ref AttrMatcher::compile. + */ + bool doMatch( const char * string_r ) const; + + private: + friend base::SafeBool::operator bool_type() const; + bool boolTest() const + { return !searchstring().empty(); } + + private: + /** Pointer to implementation */ + RWCOW_pointer _pimpl; + }; + /////////////////////////////////////////////////////////////////// + + /** \relates AttrMatcher Stream output */ + std::ostream & operator<<( std::ostream & str, const AttrMatcher & obj ); + + ///////////////////////////////////////////////////////////////// + } // namespace sat + /////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////// +} // namespace zypp +/////////////////////////////////////////////////////////////////// +#endif // ZYPP_SAT_ATTRMATCHER_H