libzypp  10.5.0
Solvable.cc
Go to the documentation of this file.
00001 /*---------------------------------------------------------------------\
00002 |                          ____ _   __ __ ___                          |
00003 |                         |__  / \ / / . \ . \                         |
00004 |                           / / \ V /|  _/  _/                         |
00005 |                          / /__ | | | | | |                           |
00006 |                         /_____||_| |_| |_|                           |
00007 |                                                                      |
00008 \---------------------------------------------------------------------*/
00012 #include <iostream>
00013 
00014 #include "zypp/base/Logger.h"
00015 #include "zypp/base/Gettext.h"
00016 #include "zypp/base/Exception.h"
00017 #include "zypp/base/Functional.h"
00018 #include "zypp/base/Collector.h"
00019 
00020 #include "zypp/sat/detail/PoolImpl.h"
00021 #include "zypp/sat/Solvable.h"
00022 #include "zypp/sat/Pool.h"
00023 #include "zypp/sat/LookupAttr.h"
00024 
00025 #include "zypp/Repository.h"
00026 #include "zypp/OnMediaLocation.h"
00027 #include "zypp/ZConfig.h"
00028 
00029 using std::endl;
00030 
00032 namespace zypp
00033 { 
00034 
00035   namespace sat
00036   { 
00037 
00038     namespace
00039     {
00040       void _doSplit( IdString & _ident, ResKind & _kind, IdString & _name )
00041       {
00042         if ( ! _ident )
00043           return;
00044 
00045         ResKind explicitKind = Solvable::SplitIdent::explicitKind( _ident.c_str() );
00046         // NOTE: kind package and srcpackage do not have namespaced ident!
00047         if ( ! explicitKind  )
00048         {
00049           _name = _ident;
00050           // No kind defaults to package
00051           if ( !_kind )
00052             _kind = ResKind::package;
00053           if ( ! ( _kind == ResKind::package || _kind == ResKind::srcpackage ) )
00054             _ident = IdString( str::form( "%s:%s", _kind.c_str(), _ident.c_str() ) );
00055         }
00056         else
00057         {
00058           // strip kind spec from name
00059           _name = IdString( ::strchr( _ident.c_str(), ':' )+1 );
00060           _kind = explicitKind;
00061           if ( _kind == ResKind::package || _kind == ResKind::srcpackage )
00062             _ident = _name;
00063         }
00064         return;
00065       }
00066     }
00067 
00068     Solvable::SplitIdent::SplitIdent( IdString ident_r )
00069     : _ident( ident_r )
00070     { _doSplit( _ident, _kind, _name ); }
00071 
00072     Solvable::SplitIdent::SplitIdent( const char * ident_r )
00073     : _ident( ident_r )
00074     { _doSplit( _ident, _kind, _name ); }
00075 
00076     Solvable::SplitIdent::SplitIdent( const std::string & ident_r )
00077     : _ident( ident_r )
00078     { _doSplit( _ident, _kind, _name ); }
00079 
00080     Solvable::SplitIdent::SplitIdent( ResKind kind_r, IdString name_r )
00081     : _ident( name_r )
00082     , _kind( kind_r )
00083     { _doSplit( _ident, _kind, _name ); }
00084 
00085     Solvable::SplitIdent::SplitIdent( ResKind kind_r, const C_Str & name_r )
00086     : _ident( name_r )
00087     , _kind( kind_r )
00088     { _doSplit( _ident, _kind, _name ); }
00089 
00090     ResKind Solvable::SplitIdent::explicitKind( const char * ident_r )
00091     {
00092       if ( ! ident_r )
00093         return ResKind();
00094 
00095       const char * sep = ::strchr( ident_r, ':' );
00096       if ( ! sep )
00097         return ResKind();
00098 
00099       ResKind ret;
00100       if ( sep-ident_r >= 4 )
00101       {
00102         switch ( ident_r[3] )
00103         {
00104           #define OUTS(K,S) if ( !::strncmp( ident_r, ResKind::K.c_str(), S ) && ident_r[S] == ':' ) ret = ResKind::K
00105           //             ----v
00106           case 'c': OUTS( patch, 5 );       break;
00107           case 'd': OUTS( product, 7 );     break;
00108           case 'k': OUTS( package, 7 );     break;
00109           case 'p': OUTS( srcpackage, 10 ); break;
00110           case 't': OUTS( pattern, 7 );     break;
00111           #undef OUTS
00112         }
00113       }
00114       return ret;
00115     }
00116 
00118 
00119     const Solvable Solvable::noSolvable;
00120 
00122 
00123     ::_Solvable * Solvable::get() const
00124     { return myPool().getSolvable( _id ); }
00125 
00126 #define NO_SOLVABLE_RETURN( VAL ) \
00127     ::_Solvable * _solvable( get() ); \
00128     if ( ! _solvable ) return VAL
00129 
00130     Solvable Solvable::nextInPool() const
00131     { return Solvable( myPool().getNextId( _id ) ); }
00132 
00133     Solvable Solvable::nextInRepo() const
00134     {
00135       NO_SOLVABLE_RETURN( noSolvable );
00136       for ( detail::SolvableIdType next = _id+1; next < unsigned(_solvable->repo->end); ++next )
00137       {
00138         ::_Solvable * nextS( myPool().getSolvable( next ) );
00139         if ( nextS && nextS->repo == _solvable->repo )
00140         {
00141           return Solvable( next );
00142         }
00143       }
00144       return noSolvable;
00145     }
00146 
00147     Repository Solvable::repository() const
00148     {
00149       NO_SOLVABLE_RETURN( Repository::noRepository );
00150       return Repository( _solvable->repo );
00151     }
00152 
00153     bool Solvable::isSystem() const
00154     {
00155       NO_SOLVABLE_RETURN( _id == detail::systemSolvableId );
00156       return myPool().isSystemRepo( _solvable->repo );
00157     }
00158 
00159     bool Solvable::onSystemByUser() const
00160     {
00161       return isSystem() && myPool().isOnSystemByUser( ident() );
00162     }
00163 
00164     IdString Solvable::ident() const
00165     {
00166       NO_SOLVABLE_RETURN( IdString() );
00167       return IdString( _solvable->name );
00168     }
00169 
00170     std::string Solvable::lookupStrAttribute( const SolvAttr & attr ) const
00171     {
00172       NO_SOLVABLE_RETURN( std::string() );
00173       const char * s = ::solvable_lookup_str( _solvable, attr.id() );
00174       return s ? s : std::string();
00175     }
00176 
00177     std::string Solvable::lookupStrAttribute( const SolvAttr & attr, const Locale & lang_r ) const
00178     {
00179       NO_SOLVABLE_RETURN( std::string() );
00180       const char * s = 0;
00181       if ( lang_r == Locale::noCode )
00182       {
00183         s = ::solvable_lookup_str_poollang( _solvable, attr.id() );
00184       }
00185       else
00186       {
00187         for ( Locale l( lang_r ); l != Locale::noCode; l = l.fallback() )
00188           if ( (s = ::solvable_lookup_str_lang( _solvable, attr.id(), l.code().c_str(), 0 )) )
00189             return s;
00190           // here: no matching locale, so use default
00191           s = ::solvable_lookup_str_lang( _solvable, attr.id(), 0, 0 );
00192       }
00193       return s ? s : std::string();
00194    }
00195 
00196     unsigned Solvable::lookupNumAttribute( const SolvAttr & attr ) const
00197     {
00198       NO_SOLVABLE_RETURN( 0 );
00199       return ::solvable_lookup_num( _solvable, attr.id(), 0 );
00200     }
00201 
00202     bool Solvable::lookupBoolAttribute( const SolvAttr & attr ) const
00203     {
00204       NO_SOLVABLE_RETURN( false );
00205       return ::solvable_lookup_bool( _solvable, attr.id() );
00206     }
00207 
00208     detail::IdType Solvable::lookupIdAttribute( const SolvAttr & attr ) const
00209     {
00210       NO_SOLVABLE_RETURN( detail::noId );
00211       return ::solvable_lookup_id( _solvable, attr.id() );
00212     }
00213 
00214     CheckSum Solvable::lookupCheckSumAttribute( const SolvAttr & attr ) const
00215     {
00216       NO_SOLVABLE_RETURN( CheckSum() );
00217       detail::IdType chksumtype = 0;
00218       const char * s = ::solvable_lookup_checksum( _solvable, attr.id(), &chksumtype );
00219       if ( ! s )
00220         return CheckSum();
00221       switch ( chksumtype )
00222       {
00223         case REPOKEY_TYPE_MD5:    return CheckSum::md5( s );
00224         case REPOKEY_TYPE_SHA1:   return CheckSum::sha1( s );
00225         case REPOKEY_TYPE_SHA256: return CheckSum::sha256( s );
00226       }
00227       return CheckSum( std::string(), s ); // try to autodetect
00228     }
00229 
00231     namespace
00232     {
00233       inline Pathname lookupDatadirIn( Repository repor_r )
00234       {
00235         static const sat::SolvAttr susetagsDatadir( "susetags:datadir" );
00236         Pathname ret;
00237         // First look for repo attribute "susetags:datadir". If not found,
00238         // look into the solvables as Code11 libsolv placed it there.
00239         sat::LookupRepoAttr datadir( susetagsDatadir, repor_r );
00240         if ( ! datadir.empty() )
00241           ret = datadir.begin().asString();
00242         else
00243         {
00244           sat::LookupAttr datadir( susetagsDatadir, repor_r );
00245           if ( ! datadir.empty() )
00246             ret = datadir.begin().asString();
00247         }
00248         return ret;
00249       }
00250     }
00252 
00253     OnMediaLocation Solvable::lookupLocation() const
00254     {
00255       NO_SOLVABLE_RETURN( OnMediaLocation() );
00256       // medianumber and path
00257       unsigned medianr;
00258       char * file = ::solvable_get_location( _solvable, &medianr );
00259       if ( ! file )
00260         return OnMediaLocation();
00261 
00262       OnMediaLocation ret;
00263 
00264       Pathname path;
00265       switch ( repository().info().type().toEnum() )
00266       {
00267         case repo::RepoType::NONE_e:
00268         {
00269           path = lookupDatadirIn( repository() );
00270           if ( ! path.empty() )
00271             repository().info().setProbedType( repo::RepoType::YAST2_e );
00272         }
00273         break;
00274 
00275         case repo::RepoType::YAST2_e:
00276         {
00277           path = lookupDatadirIn( repository() );
00278           if ( path.empty() )
00279             path = "suse";
00280         }
00281         break;
00282 
00283         default:
00284           break;
00285       }
00286       ret.setLocation    ( path/file, medianr );
00287       ret.setDownloadSize( ByteCount( lookupNumAttribute( SolvAttr::downloadsize ), ByteCount::K ) );
00288       ret.setChecksum    ( lookupCheckSumAttribute( SolvAttr::checksum ) );
00289       // Not needed/available for solvables?
00290       //ret.setOpenSize    ( ByteCount( lookupNumAttribute( SolvAttr::opensize ), ByteCount::K ) );
00291       //ret.setOpenChecksum( lookupCheckSumAttribute( SolvAttr::openchecksum ) );
00292       return ret;
00293     }
00294 
00295     ResKind Solvable::kind() const
00296     {
00297       NO_SOLVABLE_RETURN( ResKind() );
00298       // detect srcpackages by 'arch'
00299       switch ( _solvable->arch )
00300       {
00301         case ARCH_SRC:
00302         case ARCH_NOSRC:
00303           return ResKind::srcpackage;
00304           break;
00305       }
00306 
00307       const char * ident = IdString( _solvable->name ).c_str();
00308       const char * sep = ::strchr( ident, ':' );
00309 
00310       // no ':' in package names (hopefully)
00311       if ( ! sep )
00312         return ResKind::package;
00313 
00314       // quick check for well known kinds
00315       if ( sep-ident >= 4 )
00316       {
00317         switch ( ident[3] )
00318         {
00319 #define OUTS(K,S) if ( !::strncmp( ident, ResKind::K.c_str(), S ) ) return ResKind::K
00320           //             ----v
00321           case 'c': OUTS( patch, 5 );       break;
00322           case 'd': OUTS( product, 7 );     break;
00323           case 'k': OUTS( package, 7 );     break;
00324           case 'p': OUTS( srcpackage, 10 ); break;
00325           case 't': OUTS( pattern, 7 );     break;
00326 #undef OUTS
00327         }
00328       }
00329 
00330       // an unknown kind
00331       return ResKind( std::string( ident, sep-ident ) );
00332     }
00333 
00334     bool Solvable::isKind( const ResKind & kind_r ) const
00335     {
00336       NO_SOLVABLE_RETURN( false );
00337 
00338       // detect srcpackages by 'arch'
00339       switch ( _solvable->arch )
00340       {
00341         case ARCH_SRC:
00342         case ARCH_NOSRC:
00343           return( kind_r == ResKind::srcpackage );
00344           break;
00345       }
00346 
00347       // no ':' in package names (hopefully)
00348       const char * ident = IdString( _solvable->name ).c_str();
00349       if ( kind_r == ResKind::package )
00350       {
00351         return( ::strchr( ident, ':' ) == 0 );
00352       }
00353 
00354       // look for a 'kind:' prefix
00355       const char * kind = kind_r.c_str();
00356       unsigned     ksize = ::strlen( kind );
00357       return( ::strncmp( ident, kind, ksize ) == 0
00358               && ident[ksize] == ':' );
00359     }
00360 
00361     std::string Solvable::name() const
00362     {
00363       NO_SOLVABLE_RETURN( std::string() );
00364       const char * ident = IdString( _solvable->name ).c_str();
00365       const char * sep = ::strchr( ident, ':' );
00366       return( sep ? sep+1 : ident );
00367     }
00368 
00369     Edition Solvable::edition() const
00370     {
00371       NO_SOLVABLE_RETURN( Edition() );
00372       return Edition( _solvable->evr );
00373     }
00374 
00375     Arch Solvable::arch() const
00376     {
00377       NO_SOLVABLE_RETURN( Arch_noarch ); //ArchId() );
00378       switch ( _solvable->arch )
00379       {
00380         case ARCH_SRC:
00381         case ARCH_NOSRC:
00382           return Arch_noarch; //ArchId( ARCH_NOARCH );
00383           break;
00384       }
00385       return Arch( IdString(_solvable->arch).asString() );
00386       //return ArchId( _solvable->arch );
00387     }
00388 
00389     bool Solvable::multiversionInstall() const
00390     {
00391       return myPool().isMultiversion( ident() );
00392     }
00393 
00394     IdString Solvable::vendor() const
00395     {
00396       NO_SOLVABLE_RETURN( IdString() );
00397       return IdString( _solvable->vendor );
00398     }
00399 
00400     Capabilities Solvable::operator[]( Dep which_r ) const
00401     {
00402       switch( which_r.inSwitch() )
00403       {
00404         case Dep::PROVIDES_e:    return provides();    break;
00405         case Dep::REQUIRES_e:    return requires();    break;
00406         case Dep::CONFLICTS_e:   return conflicts();   break;
00407         case Dep::OBSOLETES_e:   return obsoletes();   break;
00408         case Dep::RECOMMENDS_e:  return recommends();  break;
00409         case Dep::SUGGESTS_e:    return suggests();    break;
00410         case Dep::ENHANCES_e:    return enhances();    break;
00411         case Dep::SUPPLEMENTS_e: return supplements(); break;
00412         case Dep::PREREQUIRES_e: return prerequires(); break;
00413       }
00414       return Capabilities();
00415     }
00416 
00417     inline Capabilities _getCapabilities( detail::IdType * idarraydata_r, ::Offset offs_r )
00418     {
00419       return offs_r ? Capabilities( idarraydata_r + offs_r ) : Capabilities();
00420     }
00421     Capabilities Solvable::provides() const
00422     {
00423       NO_SOLVABLE_RETURN( Capabilities() );
00424       return _getCapabilities( _solvable->repo->idarraydata, _solvable->provides );
00425     }
00426     Capabilities Solvable::requires() const
00427     {
00428       NO_SOLVABLE_RETURN( Capabilities() );
00429       return _getCapabilities( _solvable->repo->idarraydata, _solvable->requires );
00430     }
00431     Capabilities Solvable::conflicts() const
00432     {
00433       NO_SOLVABLE_RETURN( Capabilities() );
00434       return _getCapabilities( _solvable->repo->idarraydata, _solvable->conflicts );
00435     }
00436     Capabilities Solvable::obsoletes() const
00437     {
00438       NO_SOLVABLE_RETURN( Capabilities() );
00439       return _getCapabilities( _solvable->repo->idarraydata, _solvable->obsoletes );
00440     }
00441     Capabilities Solvable::recommends() const
00442     {
00443       NO_SOLVABLE_RETURN( Capabilities() );
00444       return _getCapabilities( _solvable->repo->idarraydata, _solvable->recommends );
00445     }
00446     Capabilities Solvable::suggests() const
00447     {
00448       NO_SOLVABLE_RETURN( Capabilities() );
00449       return _getCapabilities( _solvable->repo->idarraydata, _solvable->suggests );
00450     }
00451     Capabilities Solvable::enhances() const
00452     {
00453       NO_SOLVABLE_RETURN( Capabilities() );
00454       return _getCapabilities( _solvable->repo->idarraydata, _solvable->enhances );
00455     }
00456     Capabilities Solvable::supplements() const
00457     {
00458       NO_SOLVABLE_RETURN( Capabilities() );
00459       return _getCapabilities( _solvable->repo->idarraydata, _solvable->supplements );
00460     }
00461     Capabilities Solvable::prerequires() const
00462     {
00463       NO_SOLVABLE_RETURN( Capabilities() );
00464       // prerequires are a subset of requires
00465        ::Offset offs = _solvable->requires;
00466        return offs ? Capabilities( _solvable->repo->idarraydata + offs, detail::solvablePrereqMarker )
00467                    : Capabilities();
00468     }
00469 
00470     CapabilitySet Solvable::providesNamespace( const std::string & namespace_r ) const
00471     {
00472       NO_SOLVABLE_RETURN( CapabilitySet() );
00473       CapabilitySet ret;
00474       Capabilities caps( provides() );
00475       for_( it, caps.begin(), caps.end() )
00476       {
00477         CapDetail caprep( it->detail() );
00478         if ( str::hasPrefix( caprep.name().c_str(), namespace_r ) && *(caprep.name().c_str()+namespace_r.size()) == '(' )
00479           ret.insert( *it );
00480       }
00481       return ret;
00482    }
00483 
00484     CapabilitySet Solvable::valuesOfNamespace( const std::string & namespace_r ) const
00485     {
00486       NO_SOLVABLE_RETURN( CapabilitySet() );
00487       CapabilitySet ret;
00488       Capabilities caps( provides() );
00489       for_( it, caps.begin(), caps.end() )
00490       {
00491         CapDetail caprep( it->detail() );
00492         if ( str::hasPrefix( caprep.name().c_str(), namespace_r ) && *(caprep.name().c_str()+namespace_r.size()) == '(' )
00493         {
00494           std::string value( caprep.name().c_str()+namespace_r.size()+1 );
00495           value[value.size()-1] = '\0'; // erase the trailing ')'
00496           ret.insert( Capability( value, caprep.op(), caprep.ed() ) );
00497         }
00498       }
00499       return ret;
00500     }
00501 
00502 
00503     std::string Solvable::asString() const
00504     {
00505       NO_SOLVABLE_RETURN( (_id == detail::systemSolvableId ? "systemSolvable" : "noSolvable") );
00506       return str::form( "%s-%s.%s",
00507                         IdString( _solvable->name ).c_str(),
00508                         IdString( _solvable->evr ).c_str(),
00509                         IdString( _solvable->arch ).c_str() );
00510     }
00511 
00512     bool Solvable::identical( Solvable rhs ) const
00513     {
00514       NO_SOLVABLE_RETURN( ! rhs.get() );
00515       ::_Solvable * rhssolvable( rhs.get() );
00516       return rhssolvable && ( _solvable == rhssolvable || ::solvable_identical( _solvable, rhssolvable ) );
00517     }
00518 
00520     namespace
00521     { 
00522 
00526       int invokeOnEachSupportedLocale( Capability cap_r, function<bool (const Locale &)> fnc_r )
00527       {
00528         CapDetail detail( cap_r );
00529         if ( detail.kind() == CapDetail::EXPRESSION )
00530         {
00531           switch ( detail.capRel() )
00532           {
00533             case CapDetail::CAP_AND:
00534             case CapDetail::CAP_OR:
00535                 // expand
00536               {
00537                 int res = invokeOnEachSupportedLocale( detail.lhs(), fnc_r );
00538                 if ( res < 0 )
00539                   return res; // negative on abort.
00540                 int res2 = invokeOnEachSupportedLocale( detail.rhs(), fnc_r );
00541                 if ( res2 < 0 )
00542                   return -res + res2; // negative on abort.
00543                 return res + res2;
00544               }
00545               break;
00546 
00547             case CapDetail::CAP_NAMESPACE:
00548               if ( detail.lhs().id() == NAMESPACE_LANGUAGE )
00549               {
00550                 return ( !fnc_r || fnc_r( Locale( IdString(detail.rhs().id()) ) ) ) ? 1 : -1; // negative on abort.
00551               }
00552               break;
00553 
00554             case CapDetail::REL_NONE:
00555             case CapDetail::CAP_WITH:
00556             case CapDetail::CAP_ARCH:
00557               break; // unwanted
00558           }
00559         }
00560         return 0;
00561       }
00562 
00567       inline int invokeOnEachSupportedLocale( Capabilities cap_r, function<bool (const Locale &)> fnc_r )
00568       {
00569         int cnt = 0;
00570         for_( cit, cap_r.begin(), cap_r.end() )
00571         {
00572           int res = invokeOnEachSupportedLocale( *cit, fnc_r );
00573           if ( res < 0 )
00574             return -cnt + res; // negative on abort.
00575           cnt += res;
00576         }
00577         return cnt;
00578       }
00580 
00581       // Functor returning false if a Locale is in the set.
00582       struct NoMatchIn
00583       {
00584         NoMatchIn( const LocaleSet & locales_r ) : _locales( locales_r ) {}
00585 
00586         bool operator()( const Locale & locale_r ) const
00587         {
00588           return _locales.find( locale_r ) == _locales.end();
00589         }
00590 
00591         const LocaleSet & _locales;
00592       };
00593 
00594     } 
00595 
00596     bool Solvable::supportsLocales() const
00597     {
00598       // false_c stops on 1st Locale.
00599       return invokeOnEachSupportedLocale( supplements(), functor::false_c() ) < 0;
00600     }
00601 
00602     bool Solvable::supportsLocale( const Locale & locale_r ) const
00603     {
00604       // not_equal_to stops on == Locale.
00605       return invokeOnEachSupportedLocale( supplements(), bind( std::not_equal_to<Locale>(), locale_r, _1 ) ) < 0;
00606     }
00607 
00608     bool Solvable::supportsLocale( const LocaleSet & locales_r ) const
00609     {
00610       if ( locales_r.empty() )
00611         return false;
00612       // NoMatchIn stops if Locale is included.
00613       return invokeOnEachSupportedLocale( supplements(), NoMatchIn(locales_r) ) < 0;
00614     }
00615 
00616     bool Solvable::supportsRequestedLocales() const
00617     { return supportsLocale( myPool().getRequestedLocales() ); }
00618 
00619     void Solvable::getSupportedLocales( LocaleSet & locales_r ) const
00620     {
00621       invokeOnEachSupportedLocale( supplements(),
00622                                    functor::Collector( std::inserter( locales_r, locales_r.begin() ) ) );
00623     }
00624 
00625     /******************************************************************
00626     **
00627     **  FUNCTION NAME : operator<<
00628     **  FUNCTION TYPE : std::ostream &
00629     */
00630     std::ostream & operator<<( std::ostream & str, const Solvable & obj )
00631     {
00632       if ( ! obj )
00633         return str << (obj.isSystem() ? "systemSolvable" : "noSolvable" );
00634 
00635       return str << "(" << obj.id() << ")"
00636           << ( obj.isKind( ResKind::srcpackage ) ? "srcpackage:" : "" ) << obj.ident()
00637           << '-' << obj.edition() << '.' << obj.arch() << "("
00638           << obj.repository().alias() << ")";
00639     }
00640 
00641     /******************************************************************
00642     **
00643     **  FUNCTION NAME : dumpOn
00644     **  FUNCTION TYPE : std::ostream &
00645     */
00646     std::ostream & dumpOn( std::ostream & str, const Solvable & obj )
00647     {
00648       str << obj;
00649       if ( obj )
00650       {
00651 #define OUTS(X) if ( ! obj[Dep::X].empty() ) str << endl << " " #X " " << obj[Dep::X]
00652         OUTS(PROVIDES);
00653         OUTS(PREREQUIRES);
00654         OUTS(REQUIRES);
00655         OUTS(CONFLICTS);
00656         OUTS(OBSOLETES);
00657         OUTS(RECOMMENDS);
00658         OUTS(SUGGESTS);
00659         OUTS(ENHANCES);
00660         OUTS(SUPPLEMENTS);
00661 #undef OUTS
00662       }
00663       return str;
00664     }
00665 
00667   } // namespace sat
00670 } // namespace zypp