diff --git a/IOStreams/CMakeLists.txt b/IOStreams/CMakeLists.txt index f44cb48..09f5d84 100644 --- a/IOStreams/CMakeLists.txt +++ b/IOStreams/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory( IStreamable.test ) +add_subdirectory( OStreamable.test ) diff --git a/IOStreams/OStreamable.h b/IOStreams/OStreamable.h index fe4789c..0e3661f 100644 --- a/IOStreams/OStreamable.h +++ b/IOStreams/OStreamable.h @@ -13,6 +13,8 @@ static_assert( __cplusplus > 2020'00 ); #include +#include + namespace Alepha::Hydrogen::IOStreams ::detail:: ostreamable_module { inline namespace exports @@ -43,11 +45,10 @@ namespace Alepha::Hydrogen::IOStreams ::detail:: ostreamable_module // aggregates, so we'll go with this simple case for now... tuple_for_each( decomposed ) <=[&]( const auto &element ) { - if( not first ) os << '\t'; + if( not first ) os << FieldDelimiter; first= false; os << element; }; - os << '\n'; return os; } diff --git a/IOStreams/OStreamable.test/0.cc b/IOStreams/OStreamable.test/0.cc new file mode 100644 index 0000000..5691cdb --- /dev/null +++ b/IOStreams/OStreamable.test/0.cc @@ -0,0 +1,50 @@ +static_assert( __cplusplus > 2020'00 ); + +#include "../OStreamable.h" + +#include +#include + +#include + +#include + +#include + +namespace +{ + template< typename= Alepha::Capabilities< Alepha::auto_comparable, Alepha::IOStreams::OStreamable > > + struct Agg_core + { + int x; + int y; + int z; + }; + + using Agg= Agg_core<>; + static_assert( Alepha::Aggregate< Agg > ); + static_assert( Alepha::Capability< Agg, Alepha::IOStreams::OStreamable > ); + + + auto + stringify( const Agg &agg, const char delim ) + { + std::ostringstream oss; + oss << Alepha::IOStreams::setFieldDelimiter( delim ); + oss << agg; + return std::move( oss ).str(); + } +} + +static auto init= Alepha::Utility::enroll <=[] +{ + using namespace Alepha::Testing::exports; + using namespace Alepha::Testing::literals::test_literals; + + "Simple OStream"_test <=TableTest< stringify > + ::Cases + { + { "smoke test", { { 1, 2, 3 }, '\t' }, { "1\t2\t3" } }, + { "smoke test", { { 1, 2, 3 }, ',' }, { "1,2,3" } }, + }; +}; diff --git a/IOStreams/OStreamable.test/CMakeLists.txt b/IOStreams/OStreamable.test/CMakeLists.txt new file mode 100644 index 0000000..8ef1b19 --- /dev/null +++ b/IOStreams/OStreamable.test/CMakeLists.txt @@ -0,0 +1,3 @@ +link_libraries( unit-test ) + +unit_test( 0 ) diff --git a/IOStreams/delimiters.h b/IOStreams/delimiters.h new file mode 100644 index 0000000..955b364 --- /dev/null +++ b/IOStreams/delimiters.h @@ -0,0 +1,138 @@ +static_assert( __cplusplus > 2020'00 ); + +#pragma once + +#include + +#include +#include +#include + +#include + +namespace Alepha::Hydrogen::IOStreams ::detail:: delimiters +{ + inline namespace exports + { + enum { FieldDelimiter }; + enum { RecordDelimiter }; + } + + namespace C + { + const char defaultFieldDelimiter= '\t'; + const char defaultRecordDelimiter= '\n'; + } + + namespace storage + { + inline StaticValue< std::optional< char > > globalFieldDelimiter; + inline StaticValue< std::optional< char > > globalRecordDelimiter; + } + + inline char + globalFieldDelimiter() + { + if( not storage::globalFieldDelimiter().has_value() ) storage::globalFieldDelimiter()= C::defaultFieldDelimiter; + return storage::globalFieldDelimiter().value(); + } + + inline char + globalRecordDelimiter() + { + if( not storage::globalRecordDelimiter().has_value() ) storage::globalRecordDelimiter()= C::defaultRecordDelimiter; + return storage::globalRecordDelimiter().value(); + } + + + inline const int fieldIndex= std::ios::xalloc(); + + inline void + setFieldDelimiterOnIOS( std::ios &ios, const char ch ) + { + ios.iword( fieldIndex )= ch; + } + + inline char + getFieldDelimiter( std::ios &ios ) + { + if( ios.iword( fieldIndex ) == 0 ) setFieldDelimiterOnIOS( ios, globalFieldDelimiter() ); + + return ios.iword( fieldIndex ); + } + + inline std::ostream & + operator << ( std::ostream &os, decltype( FieldDelimiter ) ) + { + return os << getFieldDelimiter( os ); + } + + struct FieldDelimiterSetter + { + const char ch; + + friend std::ostream & + operator << ( std::ostream &os, const FieldDelimiterSetter &s ) + { + setFieldDelimiterOnIOS( os, s.ch ); + return os; + } + }; + + namespace exports + { + auto + setFieldDelimiter( const char ch ) + { + return FieldDelimiterSetter{ ch }; + } + } + + inline const int recordIndex= std::ios::xalloc(); + + inline void + setRecordDelimiterOnIOS( std::ios &ios, const char ch ) + { + ios.iword( recordIndex )= ch; + } + + inline char + getRecordDelimiter( std::ios &ios ) + { + if( ios.iword( recordIndex ) == 0 ) setRecordDelimiterOnIOS( ios, globalRecordDelimiter() ); + + return ios.iword( recordIndex ); + } + + inline std::ostream & + operator << ( std::ostream &os, decltype( RecordDelimiter ) ) + { + return os << getRecordDelimiter( os ); + } + + struct RecordDelimiterSetter + { + const char ch; + + friend std::ostream & + operator << ( std::ostream &os, const RecordDelimiterSetter &s ) + { + setRecordDelimiterOnIOS( os, s.ch ); + return os; + } + }; + + namespace exports + { + auto + setRecordDelimiter( const char ch ) + { + return RecordDelimiterSetter{ ch }; + } + } +} + +namespace Alepha::Hydrogen::IOStreams::inline exports::inline delimiters +{ + using namespace detail::delimiters::exports; +}