diff --git a/include/ebcl/Filesystem.hh b/include/ebcl/Filesystem.hh index 0799c16..89d4fe1 100644 --- a/include/ebcl/Filesystem.hh +++ b/include/ebcl/Filesystem.hh @@ -5,8 +5,8 @@ #ifndef _H_EBCL_FILESYSTEM #define _H_EBCL_FILESYSTEM -#include #include +#include namespace ebcl { @@ -17,6 +17,7 @@ enum class E_FSPathSeparator { SLASH , BACKSLASH }; +using T_OptPathSeparator = T_Optional< E_FSPathSeparator >; // The style of a filesystem path. class T_FSPathStyle @@ -72,8 +73,10 @@ class T_FSPathStyle static constexpr T_FSPathStyle Windows( ); static constexpr T_FSPathStyle System( ); }; +M_DECLARE_SWAP( T_FSPathStyle ); +// Representation of a path class T_FSPath { public: @@ -81,23 +84,31 @@ class T_FSPath private: + // Path style + T_FSPathStyle style_; + // Root will be '/' on Unix, or a drive letter on Windows. Relative // paths will have a blank root. T_String root_; T_Components components_; + + // Is the path valid? bool valid_; + // String comparison helpers + using F_StrCmp_ = int (*)( T_String const& , T_String const& ); + F_StrCmp_ cmpFunction_( ) const noexcept; + public: //---------------------------------------------------------------------- // Helpers static bool IsValidComponent( T_String const& string ) noexcept; - static bool IsValidRoot( T_String const& string ) noexcept; //---------------------------------------------------------------------- // Basic constructors and assignment operators - T_FSPath( ) noexcept; + T_FSPath( T_FSPathStyle style = T_FSPathStyle::System( ) ) noexcept; T_FSPath( T_FSPath const& other ) noexcept; T_FSPath& operator =( T_FSPath const& other ) noexcept; @@ -108,12 +119,15 @@ class T_FSPath T_FSPath& swap( T_FSPath& other ) noexcept; // Construct from a string - T_FSPath( T_String const& path ) noexcept; + T_FSPath( T_String const& path , + T_FSPathStyle style = T_FSPathStyle::System( ) ) noexcept; //---------------------------------------------------------------------- bool isValid( ) const noexcept; + T_FSPathStyle const& style( ) const noexcept; + T_String const& root( ) const noexcept; T_Components const& components( ) const noexcept; @@ -123,11 +137,12 @@ class T_FSPath //---------------------------------------------------------------------- // Convert the path into a string - T_String toString( ) const noexcept; + T_String toString( T_OptPathSeparator separator = {} ) const noexcept; // Compute a hash value for the path uint32_t computeHash( ) const noexcept; // Append to a string builder - void appendTo( T_StringBuilder& sb ) const noexcept; + void appendTo( T_StringBuilder& sb , + T_OptPathSeparator separator = {} ) const noexcept; // Comparisons. On Windows the comparison will be case-insensitive. int32_t compareTo( T_FSPath const& other ) const noexcept; diff --git a/include/ebcl/inline/Filesystem.hh b/include/ebcl/inline/Filesystem.hh index 96296f0..179d24e 100644 --- a/include/ebcl/inline/Filesystem.hh +++ b/include/ebcl/inline/Filesystem.hh @@ -32,6 +32,11 @@ inline T_FSPathStyle& T_FSPathStyle::swap( return *this; } +inline M_DEFINE_SWAP( T_FSPathStyle ) +{ + lhs.swap( rhs ); +} + /*----------------------------------------------------------------------------*/ inline constexpr bool T_FSPathStyle::caseSensitive( ) const noexcept @@ -104,21 +109,22 @@ inline constexpr T_FSPathStyle T_FSPathStyle::System( ) /*= T_FSPath =================================================================*/ -inline T_FSPath::T_FSPath( ) noexcept - : root_{ } , components_{ } , valid_{ true } +inline T_FSPath::T_FSPath( T_FSPathStyle style ) noexcept + : style_{ style } , root_{ } , components_{ } , valid_{ true } { } /*----------------------------------------------------------------------------*/ inline T_FSPath::T_FSPath( T_FSPath const& other ) noexcept - : root_{ other.root_ } , components_{ other.components_ } , - valid_{ other.valid_ } + : style_{ other.style_ } , root_{ other.root_ } , + components_{ other.components_ } , valid_{ other.valid_ } { } inline T_FSPath& T_FSPath::operator =( T_FSPath const& other ) noexcept { + style_ = other.style_; root_ = other.root_; components_ = other.components_; valid_ = other.valid_; @@ -166,6 +172,11 @@ inline bool T_FSPath::isValid( ) const noexcept return valid_; } +inline T_FSPathStyle const& T_FSPath::style( ) const noexcept +{ + return style_; +} + inline T_String const& T_FSPath::root( ) const noexcept { return root_; @@ -188,10 +199,11 @@ inline bool T_FSPath::isAbsolute( ) const noexcept /*----------------------------------------------------------------------------*/ -inline T_String T_FSPath::toString( ) const noexcept +inline T_String T_FSPath::toString( + T_OptPathSeparator separator ) const noexcept { T_StringBuilder sb; - appendTo( sb ); + appendTo( sb , separator ); return T_String{ std::move( sb ) }; } diff --git a/src/Filesystem.cc b/src/Filesystem.cc index eb527b4..c57fce5 100644 --- a/src/Filesystem.cc +++ b/src/Filesystem.cc @@ -55,7 +55,9 @@ bool T_FSPath::IsValidComponent( /*----------------------------------------------------------------------------*/ T_FSPath::T_FSPath( - T_String const& path ) noexcept + T_String const& path , + T_FSPathStyle style ) noexcept + : style_{ style } { if ( !path ) { valid_ = true; @@ -71,7 +73,7 @@ T_FSPath::T_FSPath( const auto p{ it.index( ) }; it.next( ); - if ( c != '/' && c != '\\' ) { + if ( !style_.isSeparator( c ) ) { continue; } @@ -97,7 +99,7 @@ T_FSPath::T_FSPath( const auto firstSlashPos{ slashPos[ 0 ] }; T_String firstItem{ path.substr( 0 , 1 + firstSlashPos ) }; valid_ = true; - if ( IsValidRoot( firstItem ) ) { + if ( style_.isValidRoot( firstItem ) ) { root_ = std::move( firstItem ); } @@ -124,14 +126,22 @@ uint32_t T_FSPath::computeHash( ) const noexcept uint32_t h{ ComputeHash( root_ ) }; const auto cs{ components_.size( ) }; h = ( ( h << 27 ) | ( h >> 5 ) ) ^ cs; - for ( auto i = 0u ; i < cs ; i ++ ) { - h = ( ( h << 27 ) | ( h >> 5 ) ) ^ ComputeHash( components_[ i ] ); + if ( style_.caseSensitive( ) ) { + for ( auto i = 0u ; i < cs ; i ++ ) { + h = ( ( h << 27 ) | ( h >> 5 ) ) ^ ComputeHash( components_[ i ] ); + } + } else { + for ( auto i = 0u ; i < cs ; i ++ ) { + h = ( ( h << 27 ) | ( h >> 5 ) ) + ^ ComputeHash( components_[ i ].toLower( ) ); + } } return h; } void T_FSPath::appendTo( - T_StringBuilder& sb ) const noexcept + T_StringBuilder& sb , + T_OptPathSeparator separator ) const noexcept { const auto cs{ components_.size( ) }; if ( !( root_ || cs ) ) { @@ -139,11 +149,14 @@ void T_FSPath::appendTo( return; } + const T_Character sepVal{ separator + ? ( *separator == E_FSPathSeparator::SLASH ? '/' : '\\' ) + : ( style_.pathSeparators( ) & E_FSPathSeparator::SLASH ? '/' : '\\' ) }; for ( auto i = 0u ; i < cs ; i ++ ) { if ( i == 0 ) { sb << root_; } else { - sb << M_PATHSEP_; + sb << sepVal; } sb << components_[ i ]; } @@ -151,11 +164,24 @@ void T_FSPath::appendTo( /*----------------------------------------------------------------------------*/ -#ifdef _WIN32 -# define M_CMPSTR_(A,B) (A).compareIgnoreCase( B ) -#else -# define M_CMPSTR_(A,B) (A).compare( B ) -#endif +// String comparison functions +namespace { + int CmpCi_( T_String const& a , T_String const& b ) + { + return a.compareIgnoreCase( b ); + } + int CmpCs_( T_String const& a , T_String const& b ) + { + return a.compare( b ); + } +} + +T_FSPath::F_StrCmp_ T_FSPath::cmpFunction_( ) const noexcept +{ + return style_.caseSensitive( ) ? CmpCs_ : CmpCi_; +} + +/*----------------------------------------------------------------------------*/ int32_t T_FSPath::compareTo( T_FSPath const& other ) const noexcept @@ -164,13 +190,14 @@ int32_t T_FSPath::compareTo( return 0; } - int32_t cmp{ M_CMPSTR_( root_ , other.root_ ) }; + const auto cmpFn{ cmpFunction_( ) }; + int32_t cmp{ cmpFn( root_ , other.root_ ) }; if ( cmp == 0 ) { const auto nca{ components_.size( ) } , ncb{ other.components_.size( ) } , nc{ std::min( nca , ncb ) }; for ( auto i = 0u ; i < nc && cmp == 0 ; i ++ ) { - cmp = M_CMPSTR_( components_[ i ] , other.components_[ i ] ); + cmp = cmpFn( components_[ i ] , other.components_[ i ] ); } if ( cmp == 0 ) { cmp = T_Comparator< uint32_t >::compare( nca , ncb ); @@ -220,7 +247,9 @@ bool T_FSPath::inDirectoryOf( if ( &other == this ) { return false; } - if ( M_CMPSTR_( root_ , other.root_ ) != 0 ) { + + const auto cmpFn{ cmpFunction_( ) }; + if ( cmpFn( root_ , other.root_ ) != 0 ) { return false; } @@ -230,12 +259,12 @@ bool T_FSPath::inDirectoryOf( } for ( auto i = 0u ; i < nc - 1 ; i ++ ) { - if ( M_CMPSTR_( components_[ i ] , other.components_[ i ] ) != 0 ) { + if ( cmpFn( components_[ i ] , other.components_[ i ] ) != 0 ) { return false; } } - return M_CMPSTR_( components_[ nc - 1 ] , other.components_[ nc - 1 ] ) != 0; + return cmpFn( components_[ nc - 1 ] , other.components_[ nc - 1 ] ) != 0; } bool T_FSPath::isParentOf( @@ -244,7 +273,9 @@ bool T_FSPath::isParentOf( if ( &other == this ) { return false; } - if ( M_CMPSTR_( root_ , other.root_ ) != 0 ) { + + const auto cmpFn{ cmpFunction_( ) }; + if ( cmpFn( root_ , other.root_ ) != 0 ) { return false; } @@ -254,7 +285,7 @@ bool T_FSPath::isParentOf( } for ( auto i = 0u ; i < nc ; i ++ ) { - if ( M_CMPSTR_( components_[ i ] , other.components_[ i ] ) != 0 ) { + if ( cmpFn( components_[ i ] , other.components_[ i ] ) != 0 ) { return false; } } @@ -267,7 +298,9 @@ bool T_FSPath::isAbove( if ( &other == this ) { return false; } - if ( M_CMPSTR_( root_ , other.root_ ) != 0 ) { + + const auto cmpFn{ cmpFunction_( ) }; + if ( cmpFn( root_ , other.root_ ) != 0 ) { return false; } @@ -277,7 +310,7 @@ bool T_FSPath::isAbove( } for ( auto i = 0u ; i < nc ; i ++ ) { - if ( M_CMPSTR_( components_[ i ] , other.components_[ i ] ) != 0 ) { + if ( cmpFn( components_[ i ] , other.components_[ i ] ) != 0 ) { return false; } } @@ -295,18 +328,19 @@ T_FSPath T_FSPath::makeRelative( return *this; } + const auto cmpFn{ cmpFunction_( ) }; const auto nca{ components_.size( ) } , ncb{ relTo.components_.size( ) } , nc{ std::min( nca , ncb ) }; uint32_t nCommon{ 0u }; for ( auto i = 0u ; i < nc ; i ++ ) { - if ( M_CMPSTR_( components_[ i ] , relTo.components_[ i ] ) != 0 ) { + if ( cmpFn( components_[ i ] , relTo.components_[ i ] ) != 0 ) { break; } nCommon ++; } - T_FSPath np; + T_FSPath np{ relTo.style_ }; const T_String parent{ T_String::Pooled( ".." ) }; for ( auto i = ncb ; i > nCommon ; i -- ) { np.components_.add( parent ); @@ -341,10 +375,9 @@ T_FSPath T_FSPath::canonical( ) const noexcept return *this; } - T_FSPath np; + T_FSPath np{ style_ }; np.root_ = root_; - for ( auto i = 0u ; i < nc ; i ++ ) { - auto const& cmp{ components_[ i ] }; + for ( auto const& cmp : components_ ) { if ( cmp == ".." ) { if ( np.components_.size( ) ) { np.components_.removeLast( ); diff --git a/tests/fs-path.cc b/tests/fs-path.cc index 507aef7..48bd6f3 100644 --- a/tests/fs-path.cc +++ b/tests/fs-path.cc @@ -11,9 +11,6 @@ class FSPathTest : public CppUnit::TestFixture CPPUNIT_TEST( testValidComponentOK ); CPPUNIT_TEST( testValidComponentBad ); - CPPUNIT_TEST( testValidRootOK ); - CPPUNIT_TEST( testValidRootBad ); - CPPUNIT_TEST( testConstructDefault ); CPPUNIT_TEST( testConstructValidStringRel ); CPPUNIT_TEST( testConstructValidStringAbs ); @@ -33,9 +30,6 @@ public: void testValidComponentOK( ); void testValidComponentBad( ); - void testValidRootOK( ); - void testValidRootBad( ); - void testConstructDefault( ); void testConstructValidStringRel( ); void testConstructValidStringAbs( ); @@ -80,36 +74,6 @@ void FSPathTest::testValidComponentBad( ) /*----------------------------------------------------------------------------*/ -void FSPathTest::testValidRootOK( ) -{ - CPPUNIT_ASSERT( T_FSPath::IsValidRoot( "/" ) ); - CPPUNIT_ASSERT( T_FSPath::IsValidRoot( "\\" ) ); -#ifdef _WIN32 - CPPUNIT_ASSERT( T_FSPath::IsValidRoot( "C:\\" ) ); - CPPUNIT_ASSERT( T_FSPath::IsValidRoot( "C:/" ) ); - CPPUNIT_ASSERT( T_FSPath::IsValidRoot( "z:/" ) ); - CPPUNIT_ASSERT( T_FSPath::IsValidRoot( "z:\\" ) ); -#endif -} - -void FSPathTest::testValidRootBad( ) -{ - CPPUNIT_ASSERT( !T_FSPath::IsValidRoot( "x/" ) ); - CPPUNIT_ASSERT( !T_FSPath::IsValidRoot( "x\\" ) ); - CPPUNIT_ASSERT( !T_FSPath::IsValidRoot( "/x" ) ); - CPPUNIT_ASSERT( !T_FSPath::IsValidRoot( "\\x" ) ); - CPPUNIT_ASSERT( !T_FSPath::IsValidRoot( "x" ) ); -#ifdef _WIN32 - CPPUNIT_ASSERT( !T_FSPath::IsValidRoot( "C:!" ) ); - CPPUNIT_ASSERT( !T_FSPath::IsValidRoot( "z!\\" ) ); - CPPUNIT_ASSERT( !T_FSPath::IsValidRoot( "z!/" ) ); - CPPUNIT_ASSERT( !T_FSPath::IsValidRoot( "_:\\" ) ); - CPPUNIT_ASSERT( !T_FSPath::IsValidRoot( "_:/" ) ); -#endif -} - -/*----------------------------------------------------------------------------*/ - void FSPathTest::testConstructDefault( ) { const T_FSPath test{ }; @@ -132,7 +96,7 @@ void FSPathTest::testConstructValidStringRel( ) void FSPathTest::testConstructValidStringAbs( ) { - const T_FSPath test{ "/this/is/a/test" }; + const T_FSPath test{ "/this/is/a/test" , T_FSPathStyle::Unix( ) }; CPPUNIT_ASSERT( test.isValid( ) ); CPPUNIT_ASSERT( test.root( ).equals( "/" ) ); CPPUNIT_ASSERT_EQUAL( 4u , test.components( ).size( ) ); @@ -144,7 +108,7 @@ void FSPathTest::testConstructValidStringAbs( ) void FSPathTest::testConstructInvalidString( ) { - const T_FSPath test{ "///\n" }; + const T_FSPath test{ "///\n" , T_FSPathStyle::Unix( ) }; CPPUNIT_ASSERT( !test.isValid( ) ); CPPUNIT_ASSERT( test.root( ).equals( "/" ) ); CPPUNIT_ASSERT_EQUAL( 3u , test.components( ).size( ) ); @@ -161,7 +125,7 @@ void FSPathTest::testCopyCons( ) "/this/is/a/test" , "this/is/a/test" , "///\n" }; for ( char const* test : tests ) { - const T_FSPath src{ test }; + const T_FSPath src{ test , T_FSPathStyle::Unix( ) }; const T_FSPath dest{ src }; CPPUNIT_ASSERT_EQUAL( src.isValid( ) , dest.isValid( ) ); CPPUNIT_ASSERT( src.root( ).equals( dest.root( ) ) ); @@ -179,7 +143,7 @@ void FSPathTest::testCopyAss( ) "/this/is/a/test" , "this/is/a/test" , "///\n" }; for ( char const* test : tests ) { - const T_FSPath src{ test }; + const T_FSPath src{ test , T_FSPathStyle::Unix( ) }; T_FSPath dest{ "/nope" }; dest = src; CPPUNIT_ASSERT_EQUAL( src.isValid( ) , dest.isValid( ) ); @@ -198,7 +162,7 @@ void FSPathTest::testMoveCons( ) "/this/is/a/test" , "this/is/a/test" , "///\n" }; for ( char const* test : tests ) { - const T_FSPath src{ test }; + const T_FSPath src{ test , T_FSPathStyle::Unix( ) }; T_FSPath moved{ test }; const T_FSPath dest{ std::move( moved ) }; @@ -222,7 +186,7 @@ void FSPathTest::testMoveAss( ) "/this/is/a/test" , "this/is/a/test" , "///\n" }; for ( char const* test : tests ) { - const T_FSPath src{ test }; + const T_FSPath src{ test , T_FSPathStyle::Unix( ) }; T_FSPath moved{ test }; T_FSPath dest{ "/nope" }; dest = std::move( moved ); @@ -262,7 +226,11 @@ void FSPathTest::testSwap( ) void FSPathTest::testIsRelative( ) { - T_FSPath a{ "/test" } , b{ "test" } , c{ "./test" } , d{ "/../test" } , e{}; + T_FSPath a{ "/test" , T_FSPathStyle::Unix( ) } , + b{ "test" , T_FSPathStyle::Unix( ) } , + c{ "./test" , T_FSPathStyle::Unix( ) } , + d{ "/../test" , T_FSPathStyle::Unix( ) } , + e{ T_FSPathStyle::Unix( ) }; CPPUNIT_ASSERT( !a.isRelative( ) ); CPPUNIT_ASSERT( b.isRelative( ) ); CPPUNIT_ASSERT( c.isRelative( ) ); @@ -272,7 +240,11 @@ void FSPathTest::testIsRelative( ) void FSPathTest::testIsAbsolute( ) { - T_FSPath a{ "/test" } , b{ "test" } , c{ "./test" } , d{ "/../test" } , e{}; + T_FSPath a{ "/test" , T_FSPathStyle::Unix( ) } , + b{ "test" , T_FSPathStyle::Unix( ) } , + c{ "./test" , T_FSPathStyle::Unix( ) } , + d{ "/../test" , T_FSPathStyle::Unix( ) } , + e{ T_FSPathStyle::Unix( ) }; CPPUNIT_ASSERT( a.isAbsolute( ) ); CPPUNIT_ASSERT( !b.isAbsolute( ) ); CPPUNIT_ASSERT( !c.isAbsolute( ) );