34 enum class Platform { Unix , Windows } ;
35 template <Platform>
struct PathPlatform
37 template <>
struct G::PathImp::PathPlatform<Platform::Unix> ;
38 template <> struct G::PathImp::PathPlatform<Platform::Windows> ;
43struct G::PathImp::PathPlatform<G::PathImp::Platform::Windows>
45 static std::string_view sep() noexcept
47 return {
"\\" , 1U } ;
49 static std::size_t slashpos(
const std::string & s )
noexcept
51 return s.rfind(
'\\') ;
53 static bool simple(
const std::string & s )
noexcept
55 return s.find(
'/') == std::string::npos && s.find(
'\\') == std::string::npos ;
57 static bool isdrive(
const std::string & s )
noexcept
59 return s.length() == 2U && s[1] ==
':' ;
61 static bool absolute(
const std::string & s )
noexcept
64 ( s.length() >= 3U && s[1] ==
':' && s[2] ==
'\\' ) ||
65 ( s.length() >= 1U && s[0] ==
'\\' ) ;
67 static std::size_t rootsizeImp(
const std::string & s , std::size_t offset , std::size_t parts )
noexcept
69 G_ASSERT( s.length() >= offset ) ;
70 G_ASSERT( parts == 1U || parts == 2U ) ;
71 std::size_t pos = s.find(
'\\' , offset ) ;
72 if( parts == 2U && pos != std::string::npos )
73 pos = s.find(
'\\' , pos+1U ) ;
74 return pos == std::string::npos ? s.length() : pos ;
76 static std::size_t rootsize(
const std::string & s )
noexcept
80 if( s.length() >= 3U && s.at(1U) ==
':' && s.at(2U) ==
'\\' )
82 if( s.length() >= 2U && s.at(1U) ==
':' )
84 if( s.find(R
"(\\?\UNC\)",0U,8U) == 0U )
85 return rootsizeImp(s,8U,2U) ;
86 if( s.find(R
"(\\?\)",0U,4U) == 0U && s.size() > 5U && s.at(5U) == ':' )
87 return rootsizeImp(s,4U,1U) ;
88 if( s.find(R
"(\\?\)",0U,4U) == 0U )
89 return rootsizeImp(s,4U,2U) ;
90 if( s.find(R
"(\\.\)",0U,4U) == 0U )
91 return rootsizeImp(s,4U,1U) ;
92 if( s.find(
"\\\\",0U,2U) == 0U )
93 return rootsizeImp(s,2U,2U) ;
94 if( s.find(
'\\') == 0U )
98 static void normalise( std::string & s )
101 bool special = s.find(
"\\\\",0U,2U) == 0U ;
103 if( special ) s.insert( 0U , 1U ,
'\\' ) ;
105 while( s.length() > 1U )
107 std::size_t pos = s.rfind(
'\\') ;
108 if( pos == std::string::npos ) break ;
109 if( (pos+1U) != s.length() ) break ;
110 if( pos < rootsize(s) ) break ;
114 static std::string null()
121struct G::PathImp::PathPlatform<
G::PathImp::Platform::Unix>
123 static std::string_view sep() noexcept
125 return {
"/" , 1U } ;
127 static std::size_t slashpos(
const std::string & s )
noexcept
129 return s.rfind(
'/') ;
131 static bool simple(
const std::string & s )
noexcept
133 return s.find(
'/') == std::string::npos ;
135 static bool isdrive(
const std::string & )
noexcept
139 static void normalise( std::string & s )
142 while( s.length() > 1U && s.at(s.length()-1U) ==
'/' ) s.resize(s.length()-1U) ;
144 static bool absolute(
const std::string & s )
noexcept
146 return !s.empty() && s[0] ==
'/' ;
148 static std::size_t rootsize(
const std::string & s )
noexcept
150 return s.empty() || s[0] !=
'/' ? 0U : 1U ;
152 static std::string null()
162 static bool use_posix = !G::is_windows() ;
163 using U = PathPlatform<Platform::Unix> ;
164 using W = PathPlatform<Platform::Windows> ;
165 std::string_view sep() {
return use_posix ? U::sep() : W::sep() ; }
166 void normalise( std::string & s ) { use_posix ? U::normalise(s) : W::normalise(s) ; }
167 bool simple(
const std::string & s ) {
return use_posix ? U::simple(s) : W::simple(s) ; }
168 bool isdrive(
const std::string & s ) {
return use_posix ? U::isdrive(s) : W::isdrive(s) ; }
169 bool absolute(
const std::string & s ) {
return use_posix ? U::absolute(s) : W::absolute(s); }
170 std::string null() {
return use_posix ? U::null() : W::null() ; }
171 std::size_t rootsize(
const std::string & s ) {
return use_posix ? U::rootsize(s) : W::rootsize(s) ; }
172 std::size_t slashpos(
const std::string & s ) {
return use_posix ? U::slashpos(s) : W::slashpos(s) ; }
180 std::size_t dotpos(
const std::string & s )
noexcept
182 const std::size_t npos = std::string::npos ;
183 const std::size_t sp = slashpos( s ) ;
184 const std::size_t dp = s.rfind(
'.' ) ;
187 else if( sp == npos )
195 void splitInto(
const std::string & str ,
StringArray & a )
197 std::size_t rs = rootsize( str ) ;
203 std::string root = str.substr( 0U , rs ) ;
205 a.insert( a.begin() , root ) ;
215 const std::string dot( 1U ,
'.' ) ;
216 a.erase( std::remove( a.begin() , a.end() , std::string() ) , a.end() ) ;
217 std::size_t n = a.size() ;
218 a.erase( std::remove( a.begin() , a.end() , dot ) , a.end() ) ;
219 const bool all_dots = a.empty() && n != 0U ;
223 std::string join( StringArray::const_iterator p , StringArray::const_iterator end )
227 for( ; p != end ; ++p , i++ )
229 bool drive = isdrive( str ) ;
230 bool last_is_slash = !str.empty() &&
231 ( str.at(str.length()-1U) ==
'/' || str.at(str.length()-1U) ==
'\\' ) ;
232 if( i == 1 && (drive || last_is_slash) )
235 str.append( sep().data() , sep().size() ) ;
243 return join( a.begin() , a.end() ) ;
253 PathImp::use_posix = true ;
260 PathImp::use_posix = false ;
270 PathImp::normalise( m_str ) ;
276 PathImp::normalise( m_str ) ;
280 m_str(sv_to_string(path))
282 PathImp::normalise( m_str ) ;
289 PathImp::normalise( m_str ) ;
298 PathImp::normalise( m_str ) ;
304 const std::string & tail_3 ) :
310 PathImp::normalise( m_str ) ;
316 return { PathImp::null() } ;
321 return dirname().empty() ;
326 return !empty() && m_str.size() <= PathImp::rootsize(m_str) ;
331 return PathImp::absolute( m_str ) ;
336 return !isAbsolute() ;
342 PathImp::splitInto( m_str , a ) ;
343 PathImp::purge( a ) ;
344 return a.empty() ? std::string() : a.at(a.size()-1U) ;
350 PathImp::splitInto( m_str , a ) ;
351 PathImp::purge( a ) ;
352 if( a.empty() )
return {} ;
359 std::string::size_type sp = PathImp::slashpos( m_str ) ;
360 std::string::size_type dp = PathImp::dotpos( m_str ) ;
361 if( dp != std::string::npos )
363 std::string result = m_str ;
364 result.resize( dp ) ;
365 if( (sp == std::string::npos && dp == 0U) || ((sp+1U) == dp) )
377 std::string result = m_str ;
378 std::string::size_type dp = PathImp::dotpos( m_str ) ;
379 if( dp != std::string::npos )
380 result.resize( dp ) ;
381 result.append( 1U ,
'.' ) ;
382 result.append( ext ) ;
392 PathImp::splitInto( m_str , a ) ;
393 G_ASSERT( !a.empty() ) ;
394 a.erase( a.begin() ) ;
395 return a.empty() ?
Path(
"."_sv) : join( a ) ;
406 Path result = join( *
this , tail ) ;
407 result.
swap( *
this ) ;
413 std::string::size_type pos = PathImp::dotpos(m_str) ;
415 pos == std::string::npos || (pos+1U) == m_str.length() ?
417 m_str.substr( pos+1U ) ;
420bool G::Path::replace(
const std::string_view & from ,
const std::string_view & to ,
bool ex_root )
422 std::size_t startpos = ex_root ? PathImp::rootsize(m_str) : 0U ;
429 PathImp::splitInto( m_str , a ) ;
430 if( PathImp::purge(a) ) a.emplace_back(
"." ) ;
436 if( a.empty() )
return {} ;
437 return { PathImp::join(a) } ;
446 else if( p2.
empty() )
454 a2.insert( a2.begin() , a1.begin() , a1.end() ) ;
461 auto two_dots_fn = [](
const std::string &s_){
return s_.size()==2U && s_[0] ==
'.' && s_[1] ==
'.' ; } ;
464 auto start = a.begin() ;
466 if( start != end && isAbsolute() )
469 while( start != end )
472 while( start != end && two_dots_fn(*start) )
476 auto p_dots = std::find_if( start , end , two_dots_fn ) ;
480 G_ASSERT( p_dots != a.begin() ) ;
481 G_ASSERT( a.size() >= 2U ) ;
484 bool at_start = std::next(start) == p_dots ;
485 auto p = a.erase( a.erase(--p_dots) ) ;
498 return 0 == m_str.compare( other.m_str ) ;
503 return 0 != m_str.compare( other.m_str ) ;
509 swap( m_str , other.m_str ) ;
517 return std::lexicographical_compare(
518 a_parts.begin() , a_parts.end() ,
519 b_parts.begin() , b_parts.end() ,
520 [](
const std::string & a_,
const std::string & b_){return a_.compare(b_) < 0;} ) ;
531 if( root_parts.size() == 1U && root_parts.at(0U) ==
"." ) root_parts.clear() ;
532 if( path_parts.size() == 1U && path_parts.at(0U) ==
"." ) path_parts.clear() ;
534 if( path_parts.size() < root_parts.size() )
537 auto p = std::mismatch( root_parts.begin() , root_parts.end() , path_parts.begin() ) ;
539 if( p.first == root_parts.end() && p.second == path_parts.end() )
541 else if( p.first != root_parts.end() )
544 return { PathImp::join(p.second,path_parts.end()) } ;
A Path object represents a file system path.
bool isAbsolute() const noexcept
Returns !isRelative().
Path withoutRoot() const
Returns a path without the root part.
void swap(Path &other) noexcept
Swaps this with other.
static bool less(const Path &a, const Path &b)
Compares two paths, with simple eight-bit lexicographical comparisons of each path component.
bool replace(const std::string_view &from, const std::string_view &to, bool ex_root=false)
Replaces the first occurrence of 'from' with 'to', optionally excluding the root part.
static Path join(const StringArray &parts)
Builds a path from a set of parts.
bool isRoot() const noexcept
Returns true if the path is a root, like "/", "c:", "c:/", "\\server\volume" etc.
std::string basename() const
Returns the rightmost part of the path, ignoring "." parts.
Path withoutExtension() const
Returns a path without the basename extension, if any.
Path dirname() const
Returns the path without the rightmost part, ignoring "." parts.
bool operator==(const Path &path) const noexcept(noexcept(std::string().compare(std::string())))
Comparison operator.
std::string extension() const
Returns the path's basename extension, ie.
bool simple() const
Returns true if the path has a single component (ignoring "." parts), ie.
Path & pathAppend(const std::string &tail)
Appends a filename or a relative path to this path.
static Path nullDevice()
Returns the path of the "/dev/null" special file, or equivalent.
Path() noexcept(noexcept(std::string()))
Default constructor for a zero-length path.
Path withExtension(const std::string &ext) const
Returns the path with the new basename extension.
Path collapsed() const
Returns the path with "foo/.." and "." parts removed, so far as is possible without changing the mean...
bool isRelative() const noexcept
Returns true if the path is a relative path or empty().
static void setPosixStyle()
Sets posix mode for testing purposes.
static void setWindowsStyle()
Sets windows mode for testing purposes.
StringArray split() const
Spits the path into a list of component parts (ignoring "." parts unless the whole path is "....
bool empty() const noexcept
Returns true if the path is empty.
bool operator!=(const Path &path) const noexcept(noexcept(std::string().compare(std::string())))
Comparison operator.
static Path difference(const Path &p1, const Path &p2)
Returns the relative path from p1 to p2.
static void splitIntoTokens(const std::string &in, StringArray &out, std::string_view ws, char esc='\0')
Splits the string into 'ws'-delimited tokens.
static std::string tail(std::string_view in, std::size_t pos, std::string_view default_={})
Returns the last part of the string after the given position.
static unsigned int replaceAll(std::string &s, std::string_view from, std::string_view to)
Does a global replace on string 's', replacing all occurrences of sub-string 'from' with 'to'.
static bool replace(std::string &s, std::string_view from, std::string_view to, std::size_t *pos_p=nullptr)
A std::string_view overload.
std::vector< std::string > StringArray
A std::vector of std::strings.