forked from Alepha/Alepha
Change variable substitution to work on streams.
This commit is contained in:
@ -2,6 +2,7 @@ static_assert( __cplusplus > 2020'00 );
|
||||
|
||||
#include "string_algorithms.h"
|
||||
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include <exception>
|
||||
|
||||
@ -17,11 +18,136 @@ namespace Alepha::Cavorite ::detail:: string_algorithms
|
||||
const bool debugExpansion= false or C::debug;
|
||||
const bool debugCommas= false or C::debug;
|
||||
}
|
||||
|
||||
struct VariableExpansionStreambuf
|
||||
: public std::streambuf
|
||||
{
|
||||
public:
|
||||
std::streambuf *underlying= nullptr;
|
||||
VarMap substitutions;
|
||||
std::stringbuf varName;
|
||||
char sigil;
|
||||
enum { Symbol, Normal } mode= Normal;
|
||||
|
||||
void
|
||||
writeChar( const char ch )
|
||||
{
|
||||
std::ostream current{ underlying };
|
||||
|
||||
if( mode == Normal and ch == sigil )
|
||||
{
|
||||
mode= Symbol;
|
||||
varName.str( "" );
|
||||
return;
|
||||
}
|
||||
if( mode == Symbol and ch == sigil )
|
||||
{
|
||||
mode= Normal;
|
||||
const std::string name( varName.view() );
|
||||
if( not name.empty() )
|
||||
{
|
||||
if( not substitutions.contains( name ) )
|
||||
{
|
||||
throw std::runtime_error{ "No such variable: `" + name +"`" };
|
||||
}
|
||||
if( C::debugExpansion ) error() << "Expanding variable with name `" << name << "`" << std::endl;
|
||||
current << substitutions.at( name )();
|
||||
}
|
||||
else current << sigil;
|
||||
return;
|
||||
}
|
||||
|
||||
if( mode == Symbol ) current.rdbuf( &varName );
|
||||
current << ch;
|
||||
}
|
||||
|
||||
int
|
||||
overflow( const int ch ) override
|
||||
{
|
||||
if( ch == EOF ) throw std::logic_error{ "EOF!" };
|
||||
writeChar( ch );
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::streamsize
|
||||
xsputn( const char *const data, const std::streamsize amt ) override
|
||||
{
|
||||
for( std::streamsize i= 0; i< amt; ++i ) overflow( data[ i ] );
|
||||
return amt;
|
||||
}
|
||||
|
||||
void
|
||||
drain()
|
||||
{
|
||||
if( mode != Normal ) throw std::runtime_error{ "Unterminated variable `" + varName.str() + " in expansion." };
|
||||
}
|
||||
};
|
||||
|
||||
const auto wrapperIndex= std::ios::xalloc();
|
||||
|
||||
void
|
||||
releaseWrapper( std::ios_base &ios )
|
||||
{
|
||||
auto *const streambuf= static_cast< VariableExpansionStreambuf * >( ios.pword( wrapperIndex ) );
|
||||
if( not streambuf ) throw std::logic_error{ "Attempt to remove a substitution context which doesn't exist." };
|
||||
|
||||
streambuf->drain();
|
||||
dynamic_cast< std::ostream & >( ios ).rdbuf( streambuf->underlying );
|
||||
delete streambuf;
|
||||
ios.pword( wrapperIndex )= nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
wordwrapCallback( const std::ios_base::event event, std::ios_base &ios, const int idx )
|
||||
{
|
||||
if( wrapperIndex != idx ) throw std::logic_error{ "Wrong index." };
|
||||
|
||||
if( not ios.pword( wrapperIndex ) ) return;
|
||||
|
||||
if( event == std::ios_base::erase_event ) releaseWrapper( ios );
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream &
|
||||
impl::operator << ( std::ostream &os, StartSubstitutions &¶ms )
|
||||
{
|
||||
auto streambuf= std::make_unique< VariableExpansionStreambuf >();
|
||||
streambuf->underlying= os.rdbuf( streambuf.get() );
|
||||
streambuf->substitutions= std::move( params.substitutions );
|
||||
streambuf->sigil= params.sigil;
|
||||
auto &state= os.iword( wrapperIndex );
|
||||
if( not state )
|
||||
{
|
||||
state= 1;
|
||||
os.register_callback( wordwrapCallback, wrapperIndex );
|
||||
}
|
||||
|
||||
assert( os.pword( wrapperIndex ) == nullptr );
|
||||
os.pword( wrapperIndex )= streambuf.release();
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream &
|
||||
impl::operator << ( std::ostream &os, EndSubstitutions_t )
|
||||
{
|
||||
releaseWrapper( os );
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string
|
||||
exports::expandVariables( const std::string &text, const VarMap &vars, const char sigil )
|
||||
{
|
||||
#if 1
|
||||
std::ostringstream oss;
|
||||
|
||||
oss << StartSubstitutions{ sigil, vars };
|
||||
oss << text;
|
||||
oss << EndSubstitutions;
|
||||
|
||||
return std::move( oss ).str();
|
||||
#else
|
||||
if( C::debugExpansion ) error() << "Expanding variables in " << text << std::endl;
|
||||
|
||||
std::string rv;
|
||||
@ -61,6 +187,7 @@ namespace Alepha::Cavorite ::detail:: string_algorithms
|
||||
if( C::debugExpansion ) error() << "Expansion was: `" << rv << "`" << std::endl;
|
||||
|
||||
return rv;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector< std::string >
|
||||
|
@ -36,6 +36,28 @@ namespace Alepha::inline Cavorite ::detail:: string_algorithms
|
||||
*/
|
||||
std::string expandVariables( const std::string &text, const VarMap &vars, const char sigil );
|
||||
|
||||
struct StartSubstitutions
|
||||
{
|
||||
const char sigil;
|
||||
VarMap substitutions;
|
||||
|
||||
explicit
|
||||
StartSubstitutions( const char sigil, VarMap substitutions )
|
||||
: sigil( sigil ), substitutions( std::move( substitutions ) )
|
||||
{}
|
||||
};
|
||||
|
||||
// Note that these function as a stack -- so `EndSubstitutions` will
|
||||
// terminate the top of that stack.
|
||||
constexpr struct EndSubstitutions_t {} EndSubstitutions;
|
||||
|
||||
inline namespace impl
|
||||
{
|
||||
std::ostream &operator << ( std::ostream &, StartSubstitutions && );
|
||||
|
||||
std::ostream &operator << ( std::ostream &, EndSubstitutions_t );
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a vector of strings parsed from a comma separated string.
|
||||
*
|
||||
|
Reference in New Issue
Block a user