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( LineTrackingStreambuf.test )
|
||||||
add_subdirectory( OutUnixFileBuf.test )
|
add_subdirectory( OutUnixFileBuf.test )
|
||||||
add_subdirectory( StackableStreambuf.test )
|
add_subdirectory( StackableStreambuf.test )
|
||||||
|
add_subdirectory( Filter.test )
|
||||||
|
|
||||||
target_sources( alepha PRIVATE
|
target_sources( alepha PRIVATE
|
||||||
StackableStreambuf.cc
|
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