forked from Alepha/Alepha
IOStream filter stage.
This permits creation of custom filtration stages in the IOStream streambuf stack.
This commit is contained in:
@ -4,6 +4,7 @@ add_subdirectory( streamable.test )
|
||||
add_subdirectory( LineTrackingStreambuf.test )
|
||||
add_subdirectory( OutUnixFileBuf.test )
|
||||
add_subdirectory( StackableStreambuf.test )
|
||||
add_subdirectory( Filter.test )
|
||||
|
||||
target_sources( alepha PRIVATE
|
||||
StackableStreambuf.cc
|
||||
|
213
IOStreams/Filter.h
Normal file
213
IOStreams/Filter.h
Normal file
@ -0,0 +1,213 @@
|
||||
static_assert( __cplusplus > 2020'99 );
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Alepha/Alepha.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include <Alepha/error.h>
|
||||
|
||||
#include <Alepha/IOStreams/StackableStreambuf.h>
|
||||
|
||||
namespace Alepha::Hydrogen::IOStreams ::detail:: Filter_m
|
||||
{
|
||||
namespace C
|
||||
{
|
||||
const bool debug= false;
|
||||
}
|
||||
|
||||
inline namespace exports
|
||||
{
|
||||
class Emitter;
|
||||
|
||||
Emitter nilEmitter();
|
||||
|
||||
class StreamFilter;
|
||||
|
||||
class FilteringStreambuf;
|
||||
|
||||
struct Filter_impl
|
||||
{
|
||||
std::unique_ptr< StreamFilter > filter;
|
||||
|
||||
explicit
|
||||
Filter_impl( std::unique_ptr< StreamFilter > filter )
|
||||
: filter( std::move( filter ) )
|
||||
{}
|
||||
};
|
||||
using Filter= PushStack< Filter_impl >;
|
||||
}
|
||||
|
||||
// XXX: This should form the new heart of how stackable
|
||||
// streambufs work. One writes StreamFilter objects.
|
||||
// When adding a filter to a streambuf in a stack, we build
|
||||
// a stack of filters. The stackable streambuf becomes
|
||||
// a stackable filter streambuf.
|
||||
|
||||
|
||||
class exports::Emitter
|
||||
{
|
||||
public:
|
||||
struct Impl
|
||||
{
|
||||
virtual ~Impl()= default;
|
||||
|
||||
virtual bool empty() const= 0;
|
||||
|
||||
virtual char next()= 0;
|
||||
};
|
||||
|
||||
private:
|
||||
std::unique_ptr< Impl > pimpl;
|
||||
|
||||
Impl &impl() { return *pimpl; }
|
||||
const Impl &impl() const { return *pimpl; }
|
||||
|
||||
public:
|
||||
explicit
|
||||
Emitter( std::unique_ptr< Impl > pimpl )
|
||||
: pimpl( std::move( pimpl ) ) {}
|
||||
|
||||
bool empty() const { return impl().empty(); }
|
||||
|
||||
char next() { return impl().next(); }
|
||||
|
||||
void
|
||||
drain( std::ostream &out )
|
||||
{
|
||||
while( not empty() )
|
||||
{
|
||||
out << next();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline Emitter
|
||||
exports::nilEmitter()
|
||||
{
|
||||
struct NilImpl : Emitter::Impl
|
||||
{
|
||||
bool empty() const final { return true; }
|
||||
char next() final { throw std::out_of_range( "" ); }
|
||||
};
|
||||
|
||||
return Emitter{ std::make_unique< NilImpl >() };
|
||||
}
|
||||
|
||||
class exports::StreamFilter
|
||||
: boost::noncopyable
|
||||
{
|
||||
public:
|
||||
virtual ~StreamFilter()= default;
|
||||
|
||||
virtual Emitter nextChar( char ch )= 0;
|
||||
|
||||
virtual Emitter
|
||||
nextChar( std::string_view chars )
|
||||
{
|
||||
std::size_t amount= 0;
|
||||
for( const char ch: chars )
|
||||
{
|
||||
++amount;
|
||||
auto emitter= nextChar( ch );
|
||||
if( not emitter.empty() )
|
||||
{
|
||||
return emitter;
|
||||
}
|
||||
}
|
||||
|
||||
return nilEmitter();
|
||||
}
|
||||
};
|
||||
|
||||
class exports::FilteringStreambuf
|
||||
: public virtual IOStreams::StackableStreambuf
|
||||
{
|
||||
private:
|
||||
static const std::size_t limit= 256;
|
||||
|
||||
std::unique_ptr< StreamFilter > filter;
|
||||
|
||||
Emitter nextInput= nilEmitter();
|
||||
std::string inBuffer;
|
||||
std::string outBuffer= std::string( int( limit ), '\0' );
|
||||
|
||||
public:
|
||||
explicit
|
||||
FilteringStreambuf( std::ios &stream, std::unique_ptr< StreamFilter > filter )
|
||||
: StackableStreambuf( stream ), filter( std::move( filter ) )
|
||||
{}
|
||||
|
||||
private:
|
||||
void drain() final {}
|
||||
void writeChar( char ) final { throw "Unimpl"; }
|
||||
|
||||
int
|
||||
underflow() override
|
||||
{
|
||||
if( C::debug ) error() << "Underflow called..." << std::endl;
|
||||
inBuffer.clear();
|
||||
while( inBuffer.size() < limit )
|
||||
{
|
||||
if( nextInput.empty() )
|
||||
{
|
||||
const auto next= underlying_sbumpc();
|
||||
if( next == EOF ) break;
|
||||
nextInput= filter->nextChar( next );
|
||||
continue;
|
||||
}
|
||||
inBuffer.push_back( nextInput.next() );
|
||||
}
|
||||
setg( inBuffer.data(), inBuffer.data(), inBuffer.data() + inBuffer.size() );
|
||||
if( inBuffer.empty() ) return EOF;
|
||||
|
||||
if( C::debug ) error() << "Underflow returning: " << inBuffer.front();
|
||||
return inBuffer.front();
|
||||
}
|
||||
|
||||
int
|
||||
overflow( const int nextChar ) override
|
||||
{
|
||||
sync();
|
||||
|
||||
if( nextChar != EOF )
|
||||
{
|
||||
Emitter emitter= filter->nextChar( nextChar );
|
||||
emitter.drain( out() );
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
sync() override
|
||||
{
|
||||
for( const auto next: std::string_view{ pbase(), pptr() } )
|
||||
{
|
||||
Emitter emitter= filter->nextChar( next );
|
||||
emitter.drain( out() );
|
||||
}
|
||||
out().rdbuf()->pubsync();
|
||||
|
||||
std::fill_n( begin( outBuffer ), limit, 0 );
|
||||
setg( outBuffer.data(), outBuffer.data(), outBuffer.data() + outBuffer.size() );
|
||||
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
inline void
|
||||
build_streambuf( std::ios &ios, Filter &¶ms )
|
||||
{
|
||||
new FilteringStreambuf( ios, std::move( params.filter ) );
|
||||
}
|
||||
}
|
||||
|
||||
namespace Alepha::Hydrogen::IOStreams::inline exports::inline Filter_m
|
||||
{
|
||||
using namespace detail::Filter_m::exports;
|
||||
}
|
||||
|
128
IOStreams/Filter.test/0.cc
Normal file
128
IOStreams/Filter.test/0.cc
Normal file
@ -0,0 +1,128 @@
|
||||
static_assert( __cplusplus > 2020'99 );
|
||||
|
||||
#include "../Filter.h"
|
||||
|
||||
#include <Alepha/Testing/test.h>
|
||||
#include <Alepha/Testing/TableTest.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
static auto init= Alepha::Utility::enroll <=[]
|
||||
{
|
||||
using namespace std::literals::string_literals;
|
||||
using namespace Alepha::Testing::literals;
|
||||
|
||||
using namespace Alepha::Testing::exports;
|
||||
|
||||
[]( const std::string &s )
|
||||
{
|
||||
std::stringstream iss{ s };
|
||||
|
||||
//iss >> Filter{ std::make_unique< CapitalizationFilter >() };
|
||||
|
||||
std::string rv;
|
||||
iss << "bob";
|
||||
int x;
|
||||
iss >> x;
|
||||
};
|
||||
|
||||
"Simple Filter"_test <=TableTest
|
||||
<
|
||||
[]( const std::string &s )
|
||||
-> std::string
|
||||
{
|
||||
using namespace Alepha::IOStreams::exports::Filter_m;
|
||||
struct CapitalizationFilter : StreamFilter
|
||||
{
|
||||
public:
|
||||
Emitter
|
||||
nextChar( const char ch ) override
|
||||
{
|
||||
struct EmitterImpl
|
||||
: public Emitter::Impl
|
||||
{
|
||||
char ch;
|
||||
bool read= false;
|
||||
|
||||
explicit EmitterImpl( const char ch ) : ch( ch ) {}
|
||||
|
||||
bool empty() const final { return read; }
|
||||
char
|
||||
next() final
|
||||
{
|
||||
read= true;
|
||||
std::cerr << "Emitted `" << ch << "`" << std::endl;
|
||||
return ch;
|
||||
}
|
||||
};
|
||||
|
||||
std::cerr << "Emitter built for `" << ch << "`" << std::endl;
|
||||
return Emitter{ std::make_unique< EmitterImpl >( ::toupper( ch ) ) };
|
||||
}
|
||||
};
|
||||
|
||||
std::ostringstream oss;
|
||||
|
||||
oss << Filter{ std::make_unique< CapitalizationFilter >() };
|
||||
|
||||
oss << s;
|
||||
|
||||
oss << Alepha::IOStreams::PopStack{};
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
>
|
||||
::Cases
|
||||
{
|
||||
{ "Hello", { "hello" }, "HELLO" },
|
||||
};
|
||||
|
||||
auto streamWriter= []( const std::string &s )
|
||||
{
|
||||
using namespace Alepha::IOStreams::exports::Filter_m;
|
||||
struct CapitalizationFilter : StreamFilter
|
||||
{
|
||||
public:
|
||||
Emitter
|
||||
nextChar( const char ch ) override
|
||||
{
|
||||
struct EmitterImpl
|
||||
: public Emitter::Impl
|
||||
{
|
||||
char ch;
|
||||
bool read= false;
|
||||
|
||||
explicit EmitterImpl( const char ch ) : ch( ch ) {}
|
||||
|
||||
bool empty() const final { return read; }
|
||||
char
|
||||
next() final
|
||||
{
|
||||
read= true;
|
||||
std::cerr << "Emitted `" << ch << "`" << std::endl;
|
||||
return ch;
|
||||
}
|
||||
};
|
||||
std::cerr << "Emitter built for `" << ch << "`" << std::endl;
|
||||
return Emitter{ std::make_unique< EmitterImpl >( ::toupper( ch ) ) };
|
||||
}
|
||||
};
|
||||
|
||||
std::stringstream iss{ s };
|
||||
|
||||
iss >> Filter{ std::make_unique< CapitalizationFilter >() };
|
||||
|
||||
std::string rv;
|
||||
iss >> rv;
|
||||
|
||||
iss >> Alepha::IOStreams::PopStack{};
|
||||
|
||||
return rv;
|
||||
};
|
||||
|
||||
"Simple Input Filter"_test <=TableTest< streamWriter >::Cases
|
||||
{
|
||||
{ "Hello", { "hello" }, "HELLO" },
|
||||
};
|
||||
};
|
1
IOStreams/Filter.test/CMakeLists.txt
Normal file
1
IOStreams/Filter.test/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
unit_test( 0 )
|
Reference in New Issue
Block a user