E-MailRelay

gpath.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2024 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gpath.cpp
19///
20
21#include "gdef.h"
22#include "gpath.h"
23#include "gstr.h"
24#include "gstringarray.h"
25#include "gstringview.h"
26#include "gassert.h"
27#include <algorithm> // std::swap()
28#include <utility> // std::swap()
29
30 namespace G
31{
32 namespace PathImp
33 {
34 enum class Platform { Unix , Windows } ;
35 template <Platform> struct PathPlatform /// A class template specialised by o/s in the implementation of G::Path.
36 {} ;
37 template <> struct G::PathImp::PathPlatform<Platform::Unix> ;
38 template <> struct G::PathImp::PathPlatform<Platform::Windows> ;
39 }
40}
41
42template <>
43struct G::PathImp::PathPlatform<G::PathImp::Platform::Windows>
44{
45 static std::string_view sep() noexcept
46 {
47 return { "\\" , 1U } ;
48 }
49 static std::size_t slashpos( const std::string & s ) noexcept
50 {
51 return s.rfind('\\') ;
52 }
53 static bool simple( const std::string & s ) noexcept
54 {
55 return s.find('/') == std::string::npos && s.find('\\') == std::string::npos ;
56 }
57 static bool isdrive( const std::string & s ) noexcept
58 {
59 return s.length() == 2U && s[1] == ':' ;
60 }
61 static bool absolute( const std::string & s ) noexcept
62 {
63 return
64 ( s.length() >= 3U && s[1] == ':' && s[2] == '\\' ) ||
65 ( s.length() >= 1U && s[0] == '\\' ) ; // NOLINT
66 }
67 static std::size_t rootsizeImp( const std::string & s , std::size_t offset , std::size_t parts ) noexcept
68 {
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 ;
75 }
76 static std::size_t rootsize( const std::string & s ) noexcept
77 {
78 if( s.empty() )
79 return 0U ;
80 if( s.length() >= 3U && s.at(1U) == ':' && s.at(2U) == '\\' )
81 return 3U ; // C:|...
82 if( s.length() >= 2U && s.at(1U) == ':' )
83 return 2U ; // C:...
84 if( s.find(R"(\\?\UNC\)",0U,8U) == 0U )
85 return rootsizeImp(s,8U,2U) ; // ||?|UNC|server|volume|...
86 if( s.find(R"(\\?\)",0U,4U) == 0U && s.size() > 5U && s.at(5U) == ':' )
87 return rootsizeImp(s,4U,1U) ; // ||?|C:|...
88 if( s.find(R"(\\?\)",0U,4U) == 0U )
89 return rootsizeImp(s,4U,2U) ; // ||?|server|volume|...
90 if( s.find(R"(\\.\)",0U,4U) == 0U )
91 return rootsizeImp(s,4U,1U) ; // ||.|dev|...
92 if( s.find("\\\\",0U,2U) == 0U )
93 return rootsizeImp(s,2U,2U) ; // ||server|volume|...
94 if( s.find('\\') == 0U )
95 return 1U ; // |...
96 return 0U ;
97 }
98 static void normalise( std::string & s )
99 {
100 Str::replace( s , '/' , '\\' ) ;
101 bool special = s.find("\\\\",0U,2U) == 0U ;
102 while( Str::replaceAll( s , "\\\\"_sv , "\\"_sv ) ) {;}
103 if( special ) s.insert( 0U , 1U , '\\' ) ;
104
105 while( s.length() > 1U )
106 {
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 ;
111 s.resize( pos ) ;
112 }
113 }
114 static std::string null()
115 {
116 return "NUL" ;
117 }
118} ;
119
120 template <>
121 struct G::PathImp::PathPlatform<G::PathImp::Platform::Unix> /// A unix specialisation of G::PathImp::PathPlatform used by G::Path.
122{
123 static std::string_view sep() noexcept
124 {
125 return { "/" , 1U } ;
126 }
127 static std::size_t slashpos( const std::string & s ) noexcept
128 {
129 return s.rfind('/') ;
130 }
131 static bool simple( const std::string & s ) noexcept
132 {
133 return s.find('/') == std::string::npos ;
134 }
135 static bool isdrive( const std::string & ) noexcept
136 {
137 return false ;
138 }
139 static void normalise( std::string & s )
140 {
141 while( Str::replaceAll( s , "//"_sv , "/"_sv ) ) {;}
142 while( s.length() > 1U && s.at(s.length()-1U) == '/' ) s.resize(s.length()-1U) ;
143 }
144 static bool absolute( const std::string & s ) noexcept
145 {
146 return !s.empty() && s[0] == '/' ;
147 }
148 static std::size_t rootsize( const std::string & s ) noexcept
149 {
150 return s.empty() || s[0] != '/' ? 0U : 1U ;
151 }
152 static std::string null()
153 {
154 return "/dev/null" ;
155 }
156} ;
157
158 namespace G
159{
160 namespace PathImp
161 {
162 static bool use_posix = !G::is_windows() ; // gdef.h // NOLINT bogus cert-err58-cpp
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) ; }
173 }
174}
175
176 namespace G
177{
178 namespace PathImp
179 {
180 std::size_t dotpos( const std::string & s ) noexcept
181 {
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( '.' ) ;
185 if( dp == npos )
186 return npos ;
187 else if( sp == npos )
188 return dp ;
189 else if( dp < sp )
190 return npos ;
191 else
192 return dp ;
193 }
194
195 void splitInto( const std::string & str , StringArray & a )
196 {
197 std::size_t rs = rootsize( str ) ;
198 if( str.empty() )
199 {
200 }
201 else if( rs != 0U ) // ie. absolute or like "c:foo"
202 {
203 std::string root = str.substr( 0U , rs ) ;
204 Str::splitIntoTokens( Str::tail(str,rs-1U,std::string()) , a , sep() ) ;
205 a.insert( a.begin() , root ) ;
206 }
207 else
208 {
209 Str::splitIntoTokens( str , a , sep() ) ;
210 }
211 }
212
213 bool purge( StringArray & a )
214 {
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 ;
220 return all_dots ;
221 }
222
223 std::string join( StringArray::const_iterator p , StringArray::const_iterator end )
224 {
225 std::string str ;
226 int i = 0 ;
227 for( ; p != end ; ++p , i++ )
228 {
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) )
233 ;
234 else if( i != 0 )
235 str.append( sep().data() , sep().size() ) ;
236 str.append( *p ) ;
237 }
238 return str ;
239 }
240
241 std::string join( const StringArray & a )
242 {
243 return join( a.begin() , a.end() ) ;
244 }
245 }
246}
247
248// ==
249
250#ifndef G_LIB_SMALL
251 void G::Path::setPosixStyle()
252{
253 PathImp::use_posix = true ;
254}
255#endif
256
257#ifndef G_LIB_SMALL
258 void G::Path::setWindowsStyle()
259{
260 PathImp::use_posix = false ;
261}
262#endif
263
264 G::Path::Path() noexcept(noexcept(std::string()))
265= default;
266
267 G::Path::Path( const std::string & path ) :
268 m_str(path)
269{
270 PathImp::normalise( m_str ) ;
271}
272
273 G::Path::Path( const char * path ) :
274 m_str(path)
275{
276 PathImp::normalise( m_str ) ;
277}
278
279 G::Path::Path( std::string_view path ) :
280 m_str(sv_to_string(path))
281{
282 PathImp::normalise( m_str ) ;
283}
284
285 G::Path::Path( const Path & path , const std::string & tail ) :
286 m_str(path.m_str)
287{
288 pathAppend( tail ) ;
289 PathImp::normalise( m_str ) ;
290}
291
292#ifndef G_LIB_SMALL
293 G::Path::Path( const Path & path , const std::string & tail_1 , const std::string & tail_2 ) :
294 m_str(path.m_str)
295{
296 pathAppend( tail_1 ) ;
297 pathAppend( tail_2 ) ;
298 PathImp::normalise( m_str ) ;
299}
300#endif
301
302#ifndef G_LIB_SMALL
303 G::Path::Path( const Path & path , const std::string & tail_1 , const std::string & tail_2 ,
304 const std::string & tail_3 ) :
305 m_str(path.m_str)
306{
307 pathAppend( tail_1 ) ;
308 pathAppend( tail_2 ) ;
309 pathAppend( tail_3 ) ;
310 PathImp::normalise( m_str ) ;
311}
312#endif
313
314 G::Path G::Path::nullDevice()
315{
316 return { PathImp::null() } ;
317}
318
319 bool G::Path::simple() const
320{
321 return dirname().empty() ;
322}
323
324 bool G::Path::isRoot() const noexcept
325{
326 return !empty() && m_str.size() <= PathImp::rootsize(m_str) ;
327}
328
329 bool G::Path::isAbsolute() const noexcept
330{
331 return PathImp::absolute( m_str ) ;
332}
333
334 bool G::Path::isRelative() const noexcept
335{
336 return !isAbsolute() ;
337}
338
339std::string G::Path::basename() const
340{
341 StringArray a ;
342 PathImp::splitInto( m_str , a ) ;
343 PathImp::purge( a ) ;
344 return a.empty() ? std::string() : a.at(a.size()-1U) ;
345}
346
347 G::Path G::Path::dirname() const
348{
349 StringArray a ;
350 PathImp::splitInto( m_str , a ) ;
351 PathImp::purge( a ) ;
352 if( a.empty() ) return {} ;
353 a.pop_back() ;
354 return join( a ) ;
355}
356
357 G::Path G::Path::withoutExtension() const
358{
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 )
362 {
363 std::string result = m_str ;
364 result.resize( dp ) ;
365 if( (sp == std::string::npos && dp == 0U) || ((sp+1U) == dp) )
366 result.append(".") ; // special case
367 return { result } ;
368 }
369 else
370 {
371 return *this ;
372 }
373}
374
375 G::Path G::Path::withExtension( const std::string & ext ) const
376{
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 ) ;
383 return { result } ;
384}
385
386#ifndef G_LIB_SMALL
387 G::Path G::Path::withoutRoot() const
388{
389 if( isAbsolute() )
390 {
391 StringArray a ;
392 PathImp::splitInto( m_str , a ) ;
393 G_ASSERT( !a.empty() ) ;
394 a.erase( a.begin() ) ;
395 return a.empty() ? Path("."_sv) : join( a ) ;
396 }
397 else
398 {
399 return *this ;
400 }
401}
402#endif
403
404 G::Path & G::Path::pathAppend( const std::string & tail )
405{
406 Path result = join( *this , tail ) ;
407 result.swap( *this ) ;
408 return *this ;
409}
410
411std::string G::Path::extension() const
412{
413 std::string::size_type pos = PathImp::dotpos(m_str) ;
414 return
415 pos == std::string::npos || (pos+1U) == m_str.length() ?
416 std::string() :
417 m_str.substr( pos+1U ) ;
418}
419
420 bool G::Path::replace( const std::string_view & from , const std::string_view & to , bool ex_root )
421{
422 std::size_t startpos = ex_root ? PathImp::rootsize(m_str) : 0U ;
423 return G::Str::replace( m_str , from , to , &startpos ) ;
424}
425
426 G::StringArray G::Path::split() const
427{
428 StringArray a ;
429 PathImp::splitInto( m_str , a ) ;
430 if( PathImp::purge(a) ) a.emplace_back( "." ) ;
431 return a ;
432}
433
434 G::Path G::Path::join( const StringArray & a )
435{
436 if( a.empty() ) return {} ;
437 return { PathImp::join(a) } ;
438}
439
440 G::Path G::Path::join( const Path & p1 , const Path & p2 )
441{
442 if( p1.empty() )
443 {
444 return p2 ;
445 }
446 else if( p2.empty() )
447 {
448 return p1 ;
449 }
450 else
451 {
452 StringArray a1 = p1.split() ;
453 StringArray a2 = p2.split() ;
454 a2.insert( a2.begin() , a1.begin() , a1.end() ) ;
455 return join( a2 ) ;
456 }
457}
458
459 G::Path G::Path::collapsed() const
460{
461 auto two_dots_fn = [](const std::string &s_){ return s_.size()==2U && s_[0] == '.' && s_[1] == '.' ; } ;
462
463 StringArray a = split() ;
464 auto start = a.begin() ;
465 auto end = a.end() ;
466 if( start != end && isAbsolute() )
467 ++start ;
468
469 while( start != end )
470 {
471 // step over leading double-dots -- cannot collapse
472 while( start != end && two_dots_fn(*start) )
473 ++start ;
474
475 // find collapsible double-dots
476 auto p_dots = std::find_if( start , end , two_dots_fn ) ;
477 if( p_dots == end )
478 break ; // no collapsible double-dots remaining
479
480 G_ASSERT( p_dots != a.begin() ) ;
481 G_ASSERT( a.size() >= 2U ) ;
482
483 // remove the preceding element and then the double-dots
484 bool at_start = std::next(start) == p_dots ;
485 auto p = a.erase( a.erase(--p_dots) ) ;
486
487 // re-initialise where invalidated
488 end = a.end() ;
489 if( at_start )
490 start = p ;
491 }
492
493 return join( a ) ;
494}
495
496 bool G::Path::operator==( const Path & other ) const noexcept(noexcept(std::string().compare(std::string())))
497{
498 return 0 == m_str.compare( other.m_str ) ; // noexcept since c++14 // NOLINT readability-string-compare
499}
500
501 bool G::Path::operator!=( const Path & other ) const noexcept(noexcept(std::string().compare(std::string())))
502{
503 return 0 != m_str.compare( other.m_str ) ; // noexcept since c++14 // NOLINT readability-string-compare
504}
505
506 void G::Path::swap( Path & other ) noexcept
507{
508 using std::swap ;
509 swap( m_str , other.m_str ) ;
510}
511
512#ifndef G_LIB_SMALL
513 bool G::Path::less( const Path & a , const Path & b )
514{
515 StringArray a_parts = a.split() ;
516 StringArray b_parts = b.split() ;
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;} ) ;
521}
522#endif
523
524#ifndef G_LIB_SMALL
525 G::Path G::Path::difference( const Path & root_in , const Path & path_in )
526{
527 StringArray path_parts ;
528 StringArray root_parts ;
529 if( !root_in.empty() ) root_parts = root_in.collapsed().split() ;
530 if( !path_in.empty() ) path_parts = path_in.collapsed().split() ;
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() ;
533
534 if( path_parts.size() < root_parts.size() )
535 return {} ;
536
537 auto p = std::mismatch( root_parts.begin() , root_parts.end() , path_parts.begin() ) ;
538
539 if( p.first == root_parts.end() && p.second == path_parts.end() )
540 return { "." } ;
541 else if( p.first != root_parts.end() )
542 return {} ;
543 else
544 return { PathImp::join(p.second,path_parts.end()) } ;
545}
546#endif
547
G::Path
A Path object represents a file system path.
Definition: gpath.h:82
G::Path::isAbsolute
bool isAbsolute() const noexcept
Returns !isRelative().
Definition: gpath.cpp:329
G::Path::withoutRoot
Path withoutRoot() const
Returns a path without the root part.
Definition: gpath.cpp:387
G::Path::swap
void swap(Path &other) noexcept
Swaps this with other.
Definition: gpath.cpp:506
G::Path::less
static bool less(const Path &a, const Path &b)
Compares two paths, with simple eight-bit lexicographical comparisons of each path component.
Definition: gpath.cpp:513
G::Path::replace
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.
Definition: gpath.cpp:420
G::Path::join
static Path join(const StringArray &parts)
Builds a path from a set of parts.
Definition: gpath.cpp:434
G::Path::isRoot
bool isRoot() const noexcept
Returns true if the path is a root, like "/", "c:", "c:/", "\\server\volume" etc.
Definition: gpath.cpp:324
G::Path::basename
std::string basename() const
Returns the rightmost part of the path, ignoring "." parts.
Definition: gpath.cpp:339
G::Path::withoutExtension
Path withoutExtension() const
Returns a path without the basename extension, if any.
Definition: gpath.cpp:357
G::Path::dirname
Path dirname() const
Returns the path without the rightmost part, ignoring "." parts.
Definition: gpath.cpp:347
G::Path::operator==
bool operator==(const Path &path) const noexcept(noexcept(std::string().compare(std::string())))
Comparison operator.
Definition: gpath.cpp:496
G::Path::extension
std::string extension() const
Returns the path's basename extension, ie.
Definition: gpath.cpp:411
G::Path::simple
bool simple() const
Returns true if the path has a single component (ignoring "." parts), ie.
Definition: gpath.cpp:319
G::Path::pathAppend
Path & pathAppend(const std::string &tail)
Appends a filename or a relative path to this path.
Definition: gpath.cpp:404
G::Path::nullDevice
static Path nullDevice()
Returns the path of the "/dev/null" special file, or equivalent.
Definition: gpath.cpp:314
G::Path::Path
Path() noexcept(noexcept(std::string()))
Default constructor for a zero-length path.
G::Path::withExtension
Path withExtension(const std::string &ext) const
Returns the path with the new basename extension.
Definition: gpath.cpp:375
G::Path::collapsed
Path collapsed() const
Returns the path with "foo/.." and "." parts removed, so far as is possible without changing the mean...
Definition: gpath.cpp:459
G::Path::isRelative
bool isRelative() const noexcept
Returns true if the path is a relative path or empty().
Definition: gpath.cpp:334
G::Path::setPosixStyle
static void setPosixStyle()
Sets posix mode for testing purposes.
Definition: gpath.cpp:251
G::Path::setWindowsStyle
static void setWindowsStyle()
Sets windows mode for testing purposes.
Definition: gpath.cpp:258
G::Path::split
StringArray split() const
Spits the path into a list of component parts (ignoring "." parts unless the whole path is "....
Definition: gpath.cpp:426
G::Path::empty
bool empty() const noexcept
Returns true if the path is empty.
Definition: gpath.h:237
G::Path::operator!=
bool operator!=(const Path &path) const noexcept(noexcept(std::string().compare(std::string())))
Comparison operator.
Definition: gpath.cpp:501
G::Path::difference
static Path difference(const Path &p1, const Path &p2)
Returns the relative path from p1 to p2.
Definition: gpath.cpp:525
G::Str::splitIntoTokens
static void splitIntoTokens(const std::string &in, StringArray &out, std::string_view ws, char esc='\0')
Splits the string into 'ws'-delimited tokens.
Definition: gstr.cpp:1119
G::Str::tail
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.
Definition: gstr.cpp:1322
G::Str::replaceAll
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'.
Definition: gstr.cpp:247
G::Str::replace
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.
Definition: gstr.cpp:226
gassert.h
gpath.h
gstr.h
gstringarray.h
gstringview.h
G
Low-level classes.
Definition: garg.h:36
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30
std
STL namespace.