E-MailRelay
gpath.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2023 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
30namespace 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 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 // TODO use rootsize()
64 return
65 ( s.length() >= 3U && s[1] == ':' && s[2] == '\\' ) ||
66 ( s.length() >= 1U && s[0] == '\\' ) ;
67 }
68 static std::size_t rootsizeImp( const std::string & s , std::size_t chars , std::size_t parts ) noexcept
69 {
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 ;
76 }
77 static std::size_t rootsize( const std::string & s ) noexcept
78 {
79 if( s.empty() )
80 return 0U ;
81 if( s.length() >= 3U && s.at(1U) == ':' && s.at(2U) == '\\' )
82 return 3U ; // C:|...
83 if( s.length() >= 2U && s.at(1U) == ':' )
84 return 2U ; // C:...
85 if( s.find(R"(\\?\UNC\)",0U,8U) == 0U )
86 return rootsizeImp(s,8U,2U) ; // ||?|UNC|server|volume|...
87 if( s.find(R"(\\?\)",0U,4U) == 0U && s.size() > 5U && s.at(5U) == ':' )
88 return rootsizeImp(s,4U,1U) ; // ||?|C:|...
89 if( s.find(R"(\\?\)",0U,4U) == 0U )
90 return rootsizeImp(s,4U,2U) ; // ||?|server|volume|...
91 if( s.find(R"(\\.\)",0U,4U) == 0U )
92 return rootsizeImp(s,4U,1U) ; // ||.|dev|...
93 if( s.find("\\\\",0U,2U) == 0U )
94 return rootsizeImp(s,2U,2U) ; // ||server|volume|...
95 if( s.find('\\') == 0U )
96 return 1U ; // |...
97 return 0U ;
98 }
99 static void normalise( std::string & s )
100 {
101 Str::replaceAll( s , "/"_sv , "\\"_sv ) ;
102 bool special = s.find("\\\\",0U,2U) == 0U ;
103 while( Str::replaceAll( s , "\\\\"_sv , "\\"_sv ) ) {;}
104 if( special ) s.insert( 0U , 1U , '\\' ) ;
105
106 while( s.length() > 1U )
107 {
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 ;
112 s.resize( pos ) ;
113 }
114 }
115 static std::string null()
116 {
117 return "NUL" ;
118 }
119} ;
120
121template <>
122struct G::PathImp::PathPlatform<G::PathImp::Platform::Unix> /// A unix specialisation of G::PathImp::PathPlatform used by G::Path.
123{
124 static string_view sep() noexcept
125 {
126 return { "/" , 1U } ;
127 }
128 static std::size_t slashpos( const std::string & s ) noexcept
129 {
130 return s.rfind('/') ;
131 }
132 static bool simple( const std::string & s ) noexcept
133 {
134 return s.find('/') == std::string::npos ;
135 }
136 static bool isdrive( const std::string & ) noexcept
137 {
138 return false ;
139 }
140 static void normalise( std::string & s )
141 {
142 while( Str::replaceAll( s , "//"_sv , "/"_sv ) ) {;}
143 while( s.length() > 1U && s.at(s.length()-1U) == '/' ) s.resize(s.length()-1U) ;
144 }
145 static bool absolute( const std::string & s ) noexcept
146 {
147 return !s.empty() && s[0] == '/' ;
148 }
149 static std::size_t rootsize( const std::string & s ) noexcept
150 {
151 return s.empty() || s[0] != '/' ? 0U : 1U ;
152 }
153 static std::string null()
154 {
155 return "/dev/null" ;
156 }
157} ;
158
159namespace G
160{
161 namespace PathImp
162 {
163 static bool use_posix = !G::is_windows() ; // gdef.h // NOLINT bogus cert-err58-cpp
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) ; }
174 }
175}
176
177namespace G
178{
179 namespace PathImp
180 {
181 static std::size_t dotpos( const std::string & s ) noexcept
182 {
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( '.' ) ;
186 if( dp == npos )
187 return npos ;
188 else if( sp == npos )
189 return dp ;
190 else if( dp < sp )
191 return npos ;
192 else
193 return dp ;
194 }
195
196 static void splitInto( const std::string & str , StringArray & a )
197 {
198 std::size_t rs = rootsize( str ) ;
199 if( str.empty() )
200 {
201 }
202 else if( rs != 0U ) // ie. absolute or like "c:foo"
203 {
204 std::string root = str.substr( 0U , rs ) ;
205 Str::splitIntoTokens( Str::tail(str,rs-1U,std::string()) , a , sep() ) ;
206 a.insert( a.begin() , root ) ;
207 }
208 else
209 {
210 Str::splitIntoTokens( str , a , sep() ) ;
211 }
212 }
213
214 static bool purge( StringArray & a )
215 {
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 ;
221 return all_dots ;
222 }
223
224 static std::string join( StringArray::const_iterator p , StringArray::const_iterator end )
225 {
226 std::string str ;
227 int i = 0 ;
228 for( ; p != end ; ++p , i++ )
229 {
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) )
234 ;
235 else if( i != 0 )
236 str.append( sep().data() , sep().size() ) ;
237 str.append( *p ) ;
238 }
239 return str ;
240 }
241
242 static std::string join( const StringArray & a )
243 {
244 return join( a.begin() , a.end() ) ;
245 }
246 }
247}
248
249// ==
250
251#ifndef G_LIB_SMALL
253{
254 PathImp::use_posix = true ;
255}
256#endif
257
258#ifndef G_LIB_SMALL
260{
261 PathImp::use_posix = false ;
262}
263#endif
264
266= default;
267
268G::Path::Path( const std::string & path ) :
269 m_str(path)
270{
271 PathImp::normalise( m_str ) ;
272}
273
274G::Path::Path( const char * path ) :
275 m_str(path)
276{
277 PathImp::normalise( m_str ) ;
278}
279
281 m_str(sv_to_string(path))
282{
283 PathImp::normalise( m_str ) ;
284}
285
286G::Path::Path( const Path & path , const std::string & tail ) :
287 m_str(path.m_str)
288{
289 pathAppend( tail ) ;
290 PathImp::normalise( m_str ) ;
291}
292
293#ifndef G_LIB_SMALL
294G::Path::Path( const Path & path , const std::string & tail_1 , const std::string & tail_2 ) :
295 m_str(path.m_str)
296{
297 pathAppend( tail_1 ) ;
298 pathAppend( tail_2 ) ;
299 PathImp::normalise( m_str ) ;
300}
301#endif
302
303#ifndef G_LIB_SMALL
304G::Path::Path( const Path & path , const std::string & tail_1 , const std::string & tail_2 ,
305 const std::string & tail_3 ) :
306 m_str(path.m_str)
307{
308 pathAppend( tail_1 ) ;
309 pathAppend( tail_2 ) ;
310 pathAppend( tail_3 ) ;
311 PathImp::normalise( m_str ) ;
312}
313#endif
314
315G::Path::Path( std::initializer_list<std::string> args )
316{
317 if( args.size() )
318 {
319 m_str = *args.begin() ;
320 for( const auto * p = args.begin()+1 ; p != args.end() ; ++p )
321 pathAppend( *p ) ;
322 PathImp::normalise( m_str ) ;
323 }
324}
325
327{
328 return { PathImp::null() } ;
329}
330
331bool G::Path::simple() const
332{
333 return dirname().empty() ;
334}
335
336bool G::Path::isAbsolute() const noexcept
337{
338 return PathImp::absolute( m_str ) ;
339}
340
341bool G::Path::isRelative() const noexcept
342{
343 return !isAbsolute() ;
344}
345
346std::string G::Path::basename() const
347{
348 StringArray a ;
349 PathImp::splitInto( m_str , a ) ;
350 PathImp::purge( a ) ;
351 return a.empty() ? std::string() : a.at(a.size()-1U) ;
352}
353
355{
356 StringArray a ;
357 PathImp::splitInto( m_str , a ) ;
358 PathImp::purge( a ) ;
359 if( a.empty() ) return {} ;
360 a.pop_back() ;
361 return join( a ) ;
362}
363
365{
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 )
369 {
370 std::string result = m_str ;
371 result.resize( dp ) ;
372 if( (sp == std::string::npos && dp == 0U) || ((sp+1U) == dp) )
373 result.append(".") ; // special case
374 return { result } ;
375 }
376 else
377 {
378 return *this ;
379 }
380}
381
382G::Path G::Path::withExtension( const std::string & ext ) const
383{
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 ) ;
390 return { result } ;
391}
392
393#ifndef G_LIB_SMALL
395{
396 if( isAbsolute() )
397 {
398 StringArray a ;
399 PathImp::splitInto( m_str , a ) ;
400 G_ASSERT( !a.empty() ) ;
401 a.erase( a.begin() ) ;
402 return a.empty() ? Path("."_sv) : join( a ) ;
403 }
404 else
405 {
406 return *this ;
407 }
408}
409#endif
410
411G::Path & G::Path::pathAppend( const std::string & tail )
412{
413 if( tail.empty() )
414 {
415 }
416 else if( PathImp::simple(tail) )
417 {
418 m_str.append( sv_to_string(PathImp::sep()) + tail ) ;
419 }
420 else
421 {
422 Path result = join( *this , tail ) ;
423 result.swap( *this ) ;
424 }
425 return *this ;
426}
427
428std::string G::Path::extension() const
429{
430 std::string::size_type pos = PathImp::dotpos(m_str) ;
431 return
432 pos == std::string::npos || (pos+1U) == m_str.length() ?
433 std::string() :
434 m_str.substr( pos+1U ) ;
435}
436
438{
439 StringArray a ;
440 PathImp::splitInto( m_str , a ) ;
441 if( PathImp::purge(a) ) a.push_back( "." ) ;
442 return a ;
443}
444
446{
447 if( a.empty() ) return {} ;
448 return { PathImp::join(a) } ;
449}
450
451G::Path G::Path::join( const G::Path & p1 , const G::Path & p2 )
452{
453 if( p1.empty() )
454 {
455 return p2 ;
456 }
457 else if( p2.empty() )
458 {
459 return p1 ;
460 }
461 else
462 {
463 StringArray a1 = p1.split() ;
464 StringArray a2 = p2.split() ;
465 a2.insert( a2.begin() , a1.begin() , a1.end() ) ;
466 return join( a2 ) ;
467 }
468}
469
471{
472 const std::string dots = ".." ;
473
474 StringArray a = split() ;
475 auto start = a.begin() ;
476 auto end = a.end() ;
477 if( start != end && isAbsolute() )
478 ++start ;
479
480 while( start != end )
481 {
482 // step over leading dots -- cannot collapse
483 while( start != end && *start == dots )
484 ++start ;
485
486 // find collapsable dots
487 auto p_dots = std::find( start , end , dots ) ;
488 if( p_dots == end )
489 break ; // no collapsable dots remaining
490
491 G_ASSERT( p_dots != a.begin() ) ;
492 G_ASSERT( a.size() >= 2U ) ;
493
494 // remove the preceding element and then the dots
495 bool at_start = std::next(start) == p_dots ;
496 auto p = a.erase( a.erase(--p_dots) ) ;
497
498 // re-initialise where invalidated
499 end = a.end() ;
500 if( at_start )
501 start = p ;
502 }
503
504 return join( a ) ;
505}
506
507bool G::Path::operator==( const Path & other ) const
508{
509 return m_str == other.m_str ; // noexcept only in c++14
510}
511
512bool G::Path::operator!=( const Path & other ) const
513{
514 return m_str != other.m_str ; // noexcept only in c++14
515}
516
517void G::Path::swap( Path & other ) noexcept
518{
519 using std::swap ;
520 swap( m_str , other.m_str ) ;
521}
522
523#ifndef G_LIB_SMALL
524bool G::Path::less( const G::Path & a , const G::Path & b )
525{
526 StringArray a_parts = a.split() ;
527 StringArray b_parts = b.split() ;
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;} ) ;
532}
533#endif
534
535#ifndef G_LIB_SMALL
536G::Path G::Path::difference( const G::Path & root_in , const G::Path & path_in )
537{
538 StringArray path_parts ;
539 StringArray root_parts ;
540 if( !root_in.empty() ) root_parts = root_in.collapsed().split() ;
541 if( !path_in.empty() ) path_parts = path_in.collapsed().split() ;
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() ;
544
545 if( path_parts.size() < root_parts.size() )
546 return {} ;
547
548 using Pair = std::pair<StringArray::iterator,StringArray::iterator> ;
549 Pair p = std::mismatch( root_parts.begin() , root_parts.end() , path_parts.begin() ) ;
550
551 if( p.first == root_parts.end() && p.second == path_parts.end() )
552 return { "." } ;
553 else if( p.first != root_parts.end() )
554 return {} ;
555 else
556 return { PathImp::join(p.second,path_parts.end()) } ;
557}
558#endif
559
A Path object represents a file system path.
Definition: gpath.h:73
bool isAbsolute() const noexcept
Returns !isRelative().
Definition: gpath.cpp:336
Path withoutRoot() const
Returns a path without the root part.
Definition: gpath.cpp:394
void swap(Path &other) noexcept
Swaps this with other.
Definition: gpath.cpp:517
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:524
bool operator!=(const Path &path) const
Comparison operator.
Definition: gpath.cpp:512
static Path join(const StringArray &parts)
Builds a path from a set of parts.
Definition: gpath.cpp:445
std::string basename() const
Returns the rightmost part of the path, ignoring "." parts.
Definition: gpath.cpp:346
Path withoutExtension() const
Returns a path without the basename extension, if any.
Definition: gpath.cpp:364
Path dirname() const
Returns the path without the rightmost part, ignoring "." parts.
Definition: gpath.cpp:354
std::string extension() const
Returns the path's basename extension, ie.
Definition: gpath.cpp:428
bool simple() const
Returns true if the path has a single component (ignoring "." parts), ie.
Definition: gpath.cpp:331
Path & pathAppend(const std::string &tail)
Appends a filename or a relative path to this path.
Definition: gpath.cpp:411
static Path nullDevice()
Returns the path of the "/dev/null" special file, or equivalent.
Definition: gpath.cpp:326
Path withExtension(const std::string &ext) const
Returns the path with the new basename extension.
Definition: gpath.cpp:382
Path collapsed() const
Returns the path with "foo/.." and "." parts removed, so far as is possible without changing the mean...
Definition: gpath.cpp:470
bool isRelative() const noexcept
Returns true if the path is a relative path or empty().
Definition: gpath.cpp:341
static void setPosixStyle()
Sets posix mode for testing purposes.
Definition: gpath.cpp:252
bool operator==(const Path &path) const
Comparison operator.
Definition: gpath.cpp:507
static void setWindowsStyle()
Sets windows mode for testing purposes.
Definition: gpath.cpp:259
StringArray split() const
Spits the path into a list of component parts (ignoring "." parts unless the whole path is "....
Definition: gpath.cpp:437
bool empty() const noexcept
Returns true if size() is zero.
Definition: gpath.h:212
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.
Definition: gpath.cpp:536
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'.
Definition: gstr.cpp:247
static void splitIntoTokens(const std::string &in, StringArray &out, string_view ws, char esc='\0')
Splits the string into 'ws'-delimited tokens.
Definition: gstr.cpp:1122
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.
Definition: gstr.cpp:1325
A class like c++17's std::string_view.
Definition: gstringview.h:51
Low-level classes.
Definition: garg.h:30
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30