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 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
65 ( s.length() >= 3U && s[1] ==
':' && s[2] ==
'\\' ) ||
66 ( s.length() >= 1U && s[0] ==
'\\' ) ;
68 static std::size_t rootsizeImp(
const std::string & s , std::size_t chars , std::size_t parts )
noexcept
70 G_ASSERT( s.length() >= chars ) ;
71 G_ASSERT( parts == 1U || parts == 2U ) ;
72 std::size_t pos = s.find(
'\\' , chars ) ;
73 if( parts == 2U && pos != std::string::npos )
74 pos = s.find(
'\\' , pos+1U ) ;
75 return pos == std::string::npos ? s.length() : pos ;
77 static std::size_t rootsize(
const std::string & s )
noexcept
81 if( s.length() >= 3U && s.at(1U) ==
':' && s.at(2U) ==
'\\' )
83 if( s.length() >= 2U && s.at(1U) ==
':' )
85 if( s.find(R
"(\\?\UNC\)",0U,8U) == 0U )
86 return rootsizeImp(s,8U,2U) ;
87 if( s.find(R
"(\\?\)",0U,4U) == 0U && s.size() > 5U && s.at(5U) == ':' )
88 return rootsizeImp(s,4U,1U) ;
89 if( s.find(R
"(\\?\)",0U,4U) == 0U )
90 return rootsizeImp(s,4U,2U) ;
91 if( s.find(R
"(\\.\)",0U,4U) == 0U )
92 return rootsizeImp(s,4U,1U) ;
93 if( s.find(
"\\\\",0U,2U) == 0U )
94 return rootsizeImp(s,2U,2U) ;
95 if( s.find(
'\\') == 0U )
99 static void normalise( std::string & s )
102 bool special = s.find(
"\\\\",0U,2U) == 0U ;
104 if( special ) s.insert( 0U , 1U ,
'\\' ) ;
106 while( s.length() > 1U )
108 std::size_t pos = s.rfind(
'\\') ;
109 if( pos == std::string::npos ) break ;
110 if( (pos+1U) != s.length() ) break ;
111 if( pos < rootsize(s) ) break ;
115 static std::string null()
122struct G::PathImp::PathPlatform<
G::PathImp::Platform::Unix>
124 static string_view sep() noexcept
126 return {
"/" , 1U } ;
128 static std::size_t slashpos(
const std::string & s )
noexcept
130 return s.rfind(
'/') ;
132 static bool simple(
const std::string & s )
noexcept
134 return s.find(
'/') == std::string::npos ;
136 static bool isdrive(
const std::string & )
noexcept
140 static void normalise( std::string & s )
143 while( s.length() > 1U && s.at(s.length()-1U) ==
'/' ) s.resize(s.length()-1U) ;
145 static bool absolute(
const std::string & s )
noexcept
147 return !s.empty() && s[0] ==
'/' ;
149 static std::size_t rootsize(
const std::string & s )
noexcept
151 return s.empty() || s[0] !=
'/' ? 0U : 1U ;
153 static std::string null()
163 static bool use_posix = !G::is_windows() ;
164 using U = PathPlatform<Platform::Unix> ;
165 using W = PathPlatform<Platform::Windows> ;
166 static string_view sep() {
return use_posix ? U::sep() : W::sep() ; }
167 static void normalise( std::string & s ) { use_posix ? U::normalise(s) : W::normalise(s) ; }
168 static bool simple(
const std::string & s ) {
return use_posix ? U::simple(s) : W::simple(s) ; }
169 static bool isdrive(
const std::string & s ) {
return use_posix ? U::isdrive(s) : W::isdrive(s) ; }
170 static bool absolute(
const std::string & s ) {
return use_posix ? U::absolute(s) : W::absolute(s); }
171 static std::string null() {
return use_posix ? U::null() : W::null() ; }
172 static std::size_t rootsize(
const std::string & s ) {
return use_posix ? U::rootsize(s) : W::rootsize(s) ; }
173 static std::size_t slashpos(
const std::string & s ) {
return use_posix ? U::slashpos(s) : W::slashpos(s) ; }
181 static std::size_t dotpos(
const std::string & s )
noexcept
183 const std::size_t npos = std::string::npos ;
184 const std::size_t sp = slashpos( s ) ;
185 const std::size_t dp = s.rfind(
'.' ) ;
188 else if( sp == npos )
196 static void splitInto(
const std::string & str ,
StringArray & a )
198 std::size_t rs = rootsize( str ) ;
204 std::string root = str.substr( 0U , rs ) ;
206 a.insert( a.begin() , root ) ;
216 const std::string dot( 1U ,
'.' ) ;
217 a.erase( std::remove( a.begin() , a.end() , std::string() ) , a.end() ) ;
218 std::size_t n = a.size() ;
219 a.erase( std::remove( a.begin() , a.end() , dot ) , a.end() ) ;
220 const bool all_dots = a.empty() && n != 0U ;
224 static std::string join( StringArray::const_iterator p , StringArray::const_iterator end )
228 for( ; p != end ; ++p , i++ )
230 bool drive = isdrive( str ) ;
231 bool last_is_slash = !str.empty() &&
232 ( str.at(str.length()-1U) ==
'/' || str.at(str.length()-1U) ==
'\\' ) ;
233 if( i == 1 && (drive || last_is_slash) )
236 str.append( sep().data() , sep().size() ) ;
244 return join( a.begin() , a.end() ) ;
254 PathImp::use_posix = true ;
261 PathImp::use_posix = false ;
271 PathImp::normalise( m_str ) ;
277 PathImp::normalise( m_str ) ;
281 m_str(sv_to_string(path))
283 PathImp::normalise( m_str ) ;
290 PathImp::normalise( m_str ) ;
299 PathImp::normalise( m_str ) ;
305 const std::string & tail_3 ) :
311 PathImp::normalise( m_str ) ;
319 m_str = *args.begin() ;
320 for(
const auto * p = args.begin()+1 ; p != args.end() ; ++p )
322 PathImp::normalise( m_str ) ;
328 return { PathImp::null() } ;
333 return dirname().empty() ;
338 return PathImp::absolute( m_str ) ;
343 return !isAbsolute() ;
349 PathImp::splitInto( m_str , a ) ;
350 PathImp::purge( a ) ;
351 return a.empty() ? std::string() : a.at(a.size()-1U) ;
357 PathImp::splitInto( m_str , a ) ;
358 PathImp::purge( a ) ;
359 if( a.empty() )
return {} ;
366 std::string::size_type sp = PathImp::slashpos( m_str ) ;
367 std::string::size_type dp = PathImp::dotpos( m_str ) ;
368 if( dp != std::string::npos )
370 std::string result = m_str ;
371 result.resize( dp ) ;
372 if( (sp == std::string::npos && dp == 0U) || ((sp+1U) == dp) )
384 std::string result = m_str ;
385 std::string::size_type dp = PathImp::dotpos( m_str ) ;
386 if( dp != std::string::npos )
387 result.resize( dp ) ;
388 result.append( 1U ,
'.' ) ;
389 result.append( ext ) ;
399 PathImp::splitInto( m_str , a ) ;
400 G_ASSERT( !a.empty() ) ;
401 a.erase( a.begin() ) ;
402 return a.empty() ?
Path(
"."_sv) : join( a ) ;
416 else if( PathImp::simple(tail) )
418 m_str.append( sv_to_string(PathImp::sep()) + tail ) ;
422 Path result = join( *
this , tail ) ;
423 result.
swap( *
this ) ;
430 std::string::size_type pos = PathImp::dotpos(m_str) ;
432 pos == std::string::npos || (pos+1U) == m_str.length() ?
434 m_str.substr( pos+1U ) ;
440 PathImp::splitInto( m_str , a ) ;
441 if( PathImp::purge(a) ) a.push_back(
"." ) ;
447 if( a.empty() )
return {} ;
448 return { PathImp::join(a) } ;
457 else if( p2.
empty() )
465 a2.insert( a2.begin() , a1.begin() , a1.end() ) ;
472 const std::string dots =
".." ;
475 auto start = a.begin() ;
477 if( start != end && isAbsolute() )
480 while( start != end )
483 while( start != end && *start == dots )
487 auto p_dots = std::find( start , end , dots ) ;
491 G_ASSERT( p_dots != a.begin() ) ;
492 G_ASSERT( a.size() >= 2U ) ;
495 bool at_start = std::next(start) == p_dots ;
496 auto p = a.erase( a.erase(--p_dots) ) ;
509 return m_str == other.m_str ;
514 return m_str != other.m_str ;
520 swap( m_str , other.m_str ) ;
528 return std::lexicographical_compare(
529 a_parts.begin() , a_parts.end() ,
530 b_parts.begin() , b_parts.end() ,
531 [](
const std::string & a_,
const std::string & b_){return a_.compare(b_) < 0;} ) ;
542 if( root_parts.size() == 1U && root_parts.at(0U) ==
"." ) root_parts.clear() ;
543 if( path_parts.size() == 1U && path_parts.at(0U) ==
"." ) path_parts.clear() ;
545 if( path_parts.size() < root_parts.size() )
548 using Pair = std::pair<StringArray::iterator,StringArray::iterator> ;
549 Pair p = std::mismatch( root_parts.begin() , root_parts.end() , path_parts.begin() ) ;
551 if( p.first == root_parts.end() && p.second == path_parts.end() )
553 else if( p.first != root_parts.end() )
556 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 operator!=(const Path &path) const
Comparison operator.
static Path join(const StringArray &parts)
Builds a path from a set of parts.
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.
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 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.
bool operator==(const Path &path) const
Comparison operator.
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 size() is zero.
Path()
Default constructor for a zero-length path.
static Path difference(const Path &p1, const Path &p2)
Returns the relative path from p1 to p2.
static unsigned int replaceAll(std::string &s, string_view from, string_view to)
Does a global replace on string 's', replacing all occurrences of sub-string 'from' with 'to'.
static void splitIntoTokens(const std::string &in, StringArray &out, string_view ws, char esc='\0')
Splits the string into 'ws'-delimited tokens.
static std::string tail(string_view in, std::size_t pos, string_view default_={})
Returns the last part of the string after the given position.
A class like c++17's std::string_view.
std::vector< std::string > StringArray
A std::vector of std::strings.