| /_____||_| |_| |_| |
| |
\---------------------------------------------------------------------*/
+#include <cstring>
+#define ZYPP_DBG_VAREXPAND 0
+#if ( ZYPP_DBG_VAREXPAND )
+#warning ZYPP_DBG_VAREXPAND is on
#include <iostream>
-#include <map>
-#include <algorithm>
+#include <sstream>
+using std::cout;
+using std::endl;
+#endif // ZYPP_DBG_VAREXPAND
+
+#include "zypp/base/LogTools.h"
#include "zypp/base/String.h"
-#include "zypp/repo/RepoException.h"
-#include "zypp/ZConfig.h"
-#include "RepoVariables.h"
+#include "zypp/base/Regex.h"
-using namespace std;
+#include "zypp/ZConfig.h"
+#include "zypp/Target.h"
+#include "zypp/Arch.h"
+#include "zypp/repo/RepoVariables.h"
+#include "zypp/base/NonCopyable.h"
+///////////////////////////////////////////////////////////////////
namespace zypp
{
-namespace repo
-{
+ namespace env
+ {
+ /** Use faked releasever (e.g. for 'zupper dup' to next distro version */
+ inline std::string ZYPP_REPO_RELEASEVER()
+ {
+ const char * env = getenv("ZYPP_REPO_RELEASEVER");
+ return( env ? env : "" );
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ namespace repo
+ {
+ ///////////////////////////////////////////////////////////////////
+ // RepoVarExpand
+ ///////////////////////////////////////////////////////////////////
+ namespace
+ {
+ ///////////////////////////////////////////////////////////////////
+ /// \class FindVar
+ /// \brief Helper scanning for variable definitions in a string
+ ///////////////////////////////////////////////////////////////////
+ struct FindVar
+ {
+ bool _embedded; ///< A (formerly) embedded string may have esacped \c $, \c closebrace and \c backslash
+ const char * _sbeg; ///< start of string to scan
+ const char * _vbeg; ///< [$]{variable:-word} / [$]{variable} / if embedded also on [\\]
+ const char * _nbeg; ///< ${[v]ariable:-word} / ${[v]ariable}
+ const char * _nend; ///< ${variable[:]-word} / ${variable[}]
+ const char * _vend; ///< ${variable:-word}[] / ${variable}[]
+ const char * _send; ///< end of scan (next $ or nullptr if done)
-RepoVariablesStringReplacer::RepoVariablesStringReplacer()
-{}
+ FindVar( const std::string & str_r, bool embedded_r )
+ : _embedded( embedded_r )
+ , _sbeg( str_r.c_str() )
+ , _vbeg( nullptr )
+ , _nbeg( nullptr )
+ , _nend( nullptr )
+ , _vend( nullptr )
+ , _send( findVarStart( _sbeg ) )
+ {}
-RepoVariablesStringReplacer::~RepoVariablesStringReplacer()
-{}
+ /** Nullptr in _send indicates we scanned the whole string. */
+ bool done() const
+ { return !_send; }
-std::string RepoVariablesStringReplacer::operator()( const std::string &value ) const
-{
- string newvalue(value);
-
- // $arch
- newvalue = str::gsub( newvalue,
- "$arch",
- ZConfig::instance().systemArchitecture().asString() );
- // $basearch
-
- Arch::CompatSet cset( Arch::compatSet( ZConfig::instance().systemArchitecture() ) );
- Arch::CompatSet::const_iterator it = cset.end();
- --it;
- // now at noarch
- --it;
-
- Arch basearch = *it;
- if ( basearch == Arch_noarch )
- {
- basearch = ZConfig::instance().systemArchitecture();
- }
+ /** Advance to first/next var if there is one */
+ bool nextVar()
+ {
+ if ( done() )
+ return false;
- newvalue = str::gsub( newvalue,
- "$basearch",
- basearch.asString() );
- return newvalue;
-}
+ do {
+ if ( _vbeg && !_vend ) // loop internal: no findVarEnd at current $; skip it
+ _send = findVarStart( _vbeg+1 );
+ _vbeg = _send; // next $ or null if string end
+ _nbeg = _nend = _vend = _send = nullptr;
+ if ( ! _vbeg ) // done!
+ return false;
+ } while( ! findVarEnd() );
-//////////////////////////////////////////////////////////////////////
+ return true;
+ }
-RepoVariablesUrlReplacer::RepoVariablesUrlReplacer()
-{}
+ /** Valid _vend indicates valid var data in scan. */
+ bool hasVar() const
+ { return _vend; }
-RepoVariablesUrlReplacer::~RepoVariablesUrlReplacer()
-{}
+ //
+ // Methods below are only valid if hasVar() == true
+ //
-/*
- * Replaces '$arch' and '$basearch' in the path and query part of the URL
- * with the global ZYpp values. Examples:
- *
- * ftp://user:secret@site.net/$arch/ -> ftp://user:secret@site.net/i686/
- * http://site.net/?basearch=$basearch -> http://site.net/?basearch=i386
- */
-Url RepoVariablesUrlReplacer::operator()( const Url &value ) const
-{
- Url newurl = value;
- RepoVariablesStringReplacer replacer;
- newurl.setPathData(replacer(value.getPathData()));
- newurl.setQueryString(replacer(value.getQueryString()));
+ /** Return the full var text */
+ std::string var() const
+ { return std::string( _vbeg, _vend ); }
+
+ /** Return the var name */
+ std::string varName() const
+ { return std::string( _nbeg, _nend ); }
+
+ /** Whether this is a conditional var (${..:[+-]...}) */
+ bool varIsConditional() const
+ { return( *(_vbeg+1) == '{' && *_nend == ':' ); }
+
+ /** The var type: \c \, \c $, \c - , \c +
+ * \li \c \ backslash escaped literal
+ * \li \c $ plain variable
+ * \li \c - conditional: default value
+ * \li \c + conditional: alternate value
+ */
+ int varType() const
+ { return( varIsConditional() ? *(_nend+1) : *_vbeg ); }
+
+ /** Return embedded value in conditional vars or empty string */
+ std::string varEmbedded() const
+ { return( varIsConditional() ? std::string( _nend+2, _vend-1 ) : std::string() ); }
+
+
+ /** Have unwritten data before var? */
+ bool hasVarPrefix() const
+ { return ( _sbeg != _vbeg ); }
+
+ /** Return unwritten data before var */
+ std::string varPrefix() const
+ { return std::string( _sbeg, _vbeg ); }
+
+ /** Indicate all data up to _vend were written */
+ void wroteVar()
+ { _sbeg = _vend; }
+
+ private:
+ /** Return next \c $ */
+ const char * findVarStart( const char * sbeg_r ) const
+ {
+ for ( ; *sbeg_r; ++sbeg_r )
+ if ( *sbeg_r == '$' || ( _embedded && *sbeg_r == '\\' ) )
+ return sbeg_r;
+ return nullptr;
+ }
+
+ /** Valid var name char */
+ bool isnamech( int ch ) const
+ { return ch == '_' || isalpha( ch ); }
+
+ /** Scan for a valid variable starting at _vbeg (storing the values) */
+ bool findVarEnd()
+ {
+ // asserted: *_vbeg == '$' || '\\'
+ if ( ! findVarEnd( _vbeg, _nbeg, _nend, _vend ) )
+ return false;
+ _send = findVarStart( _vend );
+ return true;
+ }
+
+ /** Skip over valid variable starting at vbeg (return end in \a vend). */
+ const char * findVarEnd( const char * vbeg ) const
+ {
+ // asserted: *_vbeg == '$'
+ const char * nbeg = nullptr;
+ const char * nend = nullptr;
+ const char * vend = nullptr;
+ findVarEnd( vbeg, nbeg, nend, vend );
+ return vend;
+ }
+
+ /** Scan for a valid variable starting at vbeg (const version returning the values). */
+ bool findVarEnd( const char * vbeg, const char *& nbeg, const char *& nend, const char *& vend ) const
+ {
+ // embedded only: handle backslash escaped chars
+ if ( *_vbeg == '\\' )
+ {
+ nbeg = vbeg+1;
+ if ( *nbeg == '$'
+ || *nbeg == '}'
+ || *nbeg == '\\' )
+ {
+ nend = vend = vbeg+2;
+ return true;
+ }
+ return false;
+ }
+
+ // asserted: *vbeg == '$'
+ // vbeg: [$]{variable:-word} / [$]{variable}
+ // nbeg: ${[v]ariable:-word} / ${[v]ariable}
+ bool braced = ( *(vbeg+1) == '{' ); //}
+ nbeg = vbeg+( braced ? 2 : 1 );
+ if ( !isnamech( *nbeg ) ) // don't allow empty var name
+ return false;
+ for ( nend = nbeg+1; isnamech( *nend ); ++nend )
+ {;} // skip over var name
+ // nend: ${variable[:]-word} / ${variable[}]
+
+ // vend: ${variable:-word}[] / ${variable}[]
+ // stay with ( vend == nullptr ) until you know it's valid
+ if ( braced )
+ {
+ if ( *nend == '}' )
+ {
+ vend = nend+1;
+ }
+ else if ( *nend == ':' )
+ {
+ const char * scan = nend+1;
+ if ( *scan == '+' || *scan == '-' )
+ {
+ ++scan;
+ // find first not escaped '}'
+ while ( *scan )
+ {
+ if ( *scan == '\\' )
+ {
+ ++scan; // next char is skipped
+ if ( *scan )
+ ++scan;
+ }
+ else if ( *scan == '$' )
+ {
+ // an embedded var?
+ if ( ! (scan = findVarEnd( scan )) )
+ return false;
+ }
+ else if ( *scan == '}' )
+ {
+ vend = scan+1; // ==> unesacped '}', we're done!
+ break;
+ }
+ else
+ ++scan; // literal
+ }
+ // ( ! *scan ) => end of string while looking for unesacped '}'
+ }
+ else
+ ; // err: ':' not followed by '+' or '-'
+ }
+ else
+ ; // err: braced name must end with '}' or ':'
+ }
+ else
+ {
+ vend = nend; // un-braced
+ }
+ return( vend != nullptr );
+ }
+ };
+
+ bool _expand( std::string &, const std::string & value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r );
+
+ inline std::string expand( const std::string & value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r )
+ {
+ std::string ret;
+ if ( ! _expand( ret, value_r, level_r, varRetriever_r ) )
+ ret = value_r;
+ return ret;
+ }
+
+ inline std::string expand( std::string && value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r )
+ {
+ std::string ret;
+ if ( ! _expand( ret, value_r, level_r, varRetriever_r ) )
+ ret = std::move(value_r);
+ return ret;
+ }
+
+ /** Expand variables in \a value_r depending on \a level-r
+ * <tt>level_r > 0</tt> may have escaped chars outside braces.
+ */
+ inline bool _expand( std::string & result_r, const std::string & value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r )
+ {
+#if ( ZYPP_DBG_VAREXPAND )
+ cout << std::string( 2*level_r, ' ' ) << "\033[7m>>" << value_r << "<<\033[27m" << endl;
+ std::ostringstream dbg;
+ const char * dbgsbeg = value_r.c_str(); // track vars we already added to dbg
+ unsigned dbgi = 0; // color 1-5 var / 6 moved value_r
+ dbg << std::string( 2*level_r, ' ' ) << ">>";
+#endif // ZYPP_DBG_VAREXPAND
+
+ bool expanded = false;
+
+ if ( ! value_r.empty() )
+ {
+ FindVar scan( value_r, level_r ); // level_r > 0 is embedded
+ while ( scan.nextVar() )
+ {
+ static const std::string _emptyValue;
+ const std::string *const knownVar = ( varRetriever_r ? varRetriever_r( scan.varName() ) : nullptr );
+ const std::string & varValue( knownVar ? *knownVar : _emptyValue );
+
+#if ( ZYPP_DBG_VAREXPAND )
+ dbg << std::string(dbgsbeg,scan._vbeg) << "\033[3" << ((dbgi%5)+1) << "m" << scan.var() << "\033[0m";
+ cout << dbg.str() << "|<< " << scan.varName() << " " << (knownVar?"("+varValue+")":"-") << " {" << scan.varEmbedded() << "}" << endl;
+ dbgsbeg = scan._vend;
+ dbgi++;
+#endif // ZYPP_DBG_VAREXPAND
+
+ bool mustSubstitute = false; // keep original text per default
+ std::string substitutionValue;
+
+ int varType = scan.varType();
+ if ( varType == '$' ) // plain var
+ {
+ if ( knownVar )
+ {
+ mustSubstitute = true;
+ substitutionValue = varValue;
+ }
+ else
+ ; // keep original text per default
+ }
+ else if ( varType == '-' ) // ':-' default value
+ {
+ mustSubstitute = true;
+ if ( varValue.empty() )
+ substitutionValue = expand( scan.varEmbedded(), level_r+1, varRetriever_r );
+ else
+ substitutionValue = varValue;
+ }
+ else if ( varType == '+' ) // ':+' alternate value
+ {
+ mustSubstitute = true;
+ if ( ! varValue.empty() )
+ substitutionValue = expand( scan.varEmbedded(), level_r+1, varRetriever_r );
+ else
+ ; // empty substitutionValue
+ }
+ else if ( varType == '\\' ) // backslash escaped literal (in varName)
+ {
+ mustSubstitute = true;
+ substitutionValue = scan.varName();
+ }
+ else
+ ; // keep original text per default
+
+ if ( mustSubstitute )
+ {
+ if ( scan.hasVarPrefix() )
+ result_r += scan.varPrefix();
+ if ( ! substitutionValue.empty() )
+ result_r += substitutionValue;
+ scan.wroteVar(); // this moves scan._sbeg so we can later see what's already written
+ }
+ }
+
+#if ( ZYPP_DBG_VAREXPAND )
+ dbg << std::string( dbgsbeg ) << (scan._sbeg == value_r.c_str() ? "<<\033[36m(moved)\033[0m" : "");
+#endif // ZYPP_DBG_VAREXPAND
+
+ // handle unwritten data:
+ if ( scan._sbeg != value_r.c_str() )
+ {
+ expanded = true;
+ if ( *scan._sbeg )
+ result_r += std::string( scan._sbeg );
+ }
+ else
+ ; // no replacements at all
+ }
+
+#if ( ZYPP_DBG_VAREXPAND )
+ dbg << "<<";
+ cout << dbg.str() << endl;
+ cout << std::string( 2*level_r, ' ' ) << "\033[36m->" << result_r << "<-\033[0m" << endl;
+#endif // ZYPP_DBG_VAREXPAND
+ return expanded;
+ }
+ } // namespace
+ ///////////////////////////////////////////////////////////////////
+
+ std::string RepoVarExpand::operator()( const std::string & value_r, VarRetriever varRetriever_r ) const
+ { return expand( value_r, 0, varRetriever_r ); }
+
+ std::string RepoVarExpand::operator()( std::string && value_r, VarRetriever varRetriever_r ) const
+ { return expand( std::move(value_r), 0, varRetriever_r ); }
+
+ ///////////////////////////////////////////////////////////////////
+ // RepoVariables*Replace
+ ///////////////////////////////////////////////////////////////////
+ namespace
+ {
+ /** \brief Provide lazy initialized repo variables
+ */
+ struct RepoVars : private zypp::base::NonCopyable
+ {
+ typedef const std::string & (RepoVars::*Getter)() const;
+
+ const std::string & arch() const
+ {
+ assertArchStr();
+ return _arch;
+ }
+
+ const std::string & basearch() const
+ {
+ assertArchStr();
+ return _basearch;
+ }
+
+ const std::string & releasever() const
+ {
+ assertReleaseverStr();
+ return _releasever;
+ }
+
+ const std::string & releaseverMajor() const
+ {
+ assertReleaseverStr();
+ return _releaseverMajor;
+ }
+
+ const std::string & releaseverMinor() const
+ {
+ assertReleaseverStr();
+ return _releaseverMinor;
+ }
+
+ private:
+ void assertArchStr() const
+ {
+ if ( _arch.empty() )
+ {
+ Arch arch( ZConfig::instance().systemArchitecture() );
+ _arch = arch.asString();
+ _basearch = arch.baseArch().asString();
+ }
+ }
+
+ void assertReleaseverStr() const
+ {
+ if ( _releasever.empty() )
+ {
+ _releasever = env::ZYPP_REPO_RELEASEVER();
+ if( _releasever.empty() )
+ _releasever = Target::distributionVersion( Pathname()/*guess*/ );
+ else
+ WAR << "ENV overwrites $releasever=" << _releasever << endl;
+
+ // split major/minor for SLE
+ std::string::size_type pos = _releasever.find( "." );
+ if ( pos == std::string::npos )
+ {
+ _releaseverMajor = _releasever;
+ _releaseverMinor.clear();
+ }
+ else
+ {
+ _releaseverMajor = _releasever.substr( 0, pos );
+ _releaseverMinor = _releasever.substr( pos+1 ) ;
+ }
+ }
+ }
+ private:
+ mutable std::string _arch;
+ mutable std::string _basearch;
+ mutable std::string _releasever;
+ mutable std::string _releaseverMajor;
+ mutable std::string _releaseverMinor;
+ };
+
+ /** \brief */
+ const std::string * repoVarLookup( const std::string & name_r )
+ {
+ RepoVars::Getter getter = nullptr;
+ switch ( name_r.size() )
+ {
+#define ASSIGN_IF(NAME,GETTER) if ( name_r == NAME ) getter = GETTER
+ case 4: ASSIGN_IF( "arch", &RepoVars::arch ); break;
+ case 8: ASSIGN_IF( "basearch", &RepoVars::basearch ); break;
+ case 10: ASSIGN_IF( "releasever", &RepoVars::releasever ); break;
+ case 16: ASSIGN_IF( "releasever_major", &RepoVars::releaseverMajor );
+ else ASSIGN_IF( "releasever_minor", &RepoVars::releaseverMinor ); break;
+#undef ASSIGN_IF
+ }
+
+ const std::string * ret = nullptr;
+ if ( getter ) // known var
+ {
+ static const RepoVars _repoVars;
+ ret = &(_repoVars.*getter)();
+ }
+ return ret;
+ }
+ } // namespace
+ ///////////////////////////////////////////////////////////////////
- return newurl;
-}
+ std::string RepoVariablesStringReplacer::operator()( const std::string & value ) const
+ {
+ return RepoVarExpand()( value, repoVarLookup );
+ }
+ std::string RepoVariablesStringReplacer::operator()( std::string && value ) const
+ {
+ return RepoVarExpand()( value, repoVarLookup );
+ }
-} // ns repo
-} // ns zypp
+ Url RepoVariablesUrlReplacer::operator()( const Url & value ) const
+ {
+ RepoVarExpand expand;
+ Url newurl( value );
+ newurl.setPathData( expand( value.getPathData(), repoVarLookup ) );
+ newurl.setQueryString( expand( value.getQueryString(), repoVarLookup ) );
+ return newurl;
+ }
-// vim: set ts=2 sts=2 sw=2 et ai:
+ } // namespace repo
+ ///////////////////////////////////////////////////////////////////
+} // namespace zypp
+///////////////////////////////////////////////////////////////////