E-MailRelay

gpath.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 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 "gstrings.h"
25#include "gstringview.h"
26#include "gassert.h"
27#include <algorithm> // std::swap()
28#include <utility> // std::swap()
29
30//| \class G::PathImp
31/// A private implementation class used by G::Path providing a set of
32/// static methods. Supports both posix-style and windows-style paths
33/// at run-time, with only the default selected at compile-time.
34///
35 class G::PathImp
36{
37 public:
38 static bool use_posix ;
39 using pos_t = std::string::size_type ;
40
41 static string_view windows_sep()
42 {
43 return {"\\",1U} ;
44 }
45 static pos_t windows_slashpos( const std::string & s )
46 {
47 return s.rfind('\\') ;
48 }
49 static bool windows_simple( const std::string & s )
50 {
51 return s.find('/') == std::string::npos && s.find('\\') == std::string::npos ;
52 }
53 static bool windows_absolute( const std::string & s )
54 {
55 return
56 ( s.length() >= 3U && s.at(1U) == ':' && s.at(2U) == '\\' ) ||
57 ( s.length() >= 1U && s.at(0U) == '\\' ) ;
58 }
59 static pos_t windows_rootsize( const std::string & s , std::size_t chars , std::size_t parts )
60 {
61 G_ASSERT( s.length() >= chars ) ;
62 G_ASSERT( parts == 1U || parts == 2U ) ;
63 pos_t pos = s.find( '\\' , chars ) ;
64 if( parts == 2U && pos != std::string::npos )
65 pos = s.find( '\\' , pos+1U ) ;
66 return pos == std::string::npos ? s.length() : pos ;
67 }
68 static pos_t windows_rootsize( const std::string & s )
69 {
70 if( s.empty() )
71 return 0U ;
72 if( s.length() >= 3U && s.at(1U) == ':' && s.at(2U) == '\\' )
73 return 3U ; // C:|...
74 if( s.length() >= 2U && s.at(1U) == ':' )
75 return 2U ; // C:...
76 if( s.find("\\\\?\\UNC\\") == 0U )
77 return windows_rootsize(s,8U,2U) ; // ||?|UNC|server|volume|...
78 if( s.find("\\\\?\\") == 0U && s.size() > 5U && s.at(5U) == ':' )
79 return windows_rootsize(s,4U,1U) ; // ||?|C:|...
80 if( s.find("\\\\?\\") == 0U )
81 return windows_rootsize(s,4U,2U) ; // ||?|server|volume|...
82 if( s.find("\\\\.\\") == 0U )
83 return windows_rootsize(s,4U,1U) ; // ||.|dev|...
84 if( s.find("\\\\") == 0U )
85 return windows_rootsize(s,2U,2U) ; // ||server|volume|...
86 if( s.find('\\') == 0U )
87 return 1U ; // |...
88 return 0U ;
89 }
90 static void windows_normalise( std::string & s )
91 {
92 Str::replaceAll( s , "/" , "\\" ) ;
93 bool special = s.find("\\\\") == 0U ;
94 while( Str::replaceAll( s , "\\\\" , "\\" ) ) {;}
95 if( special ) s = "\\" + s ;
96
97 while( s.length() > 1U )
98 {
99 pos_t pos = s.rfind('\\') ;
100 if( pos == std::string::npos ) break ;
101 if( (pos+1U) != s.length() ) break ;
102 if( pos < windows_rootsize(s) ) break ;
103 s.resize( pos ) ;
104 }
105 }
106 static std::string windows_null()
107 {
108 return "NUL" ;
109 }
110
111 static string_view posix_sep()
112 {
113 return {"/",1U} ;
114 }
115 static pos_t posix_slashpos( const std::string & s )
116 {
117 return s.rfind('/') ;
118 }
119 static bool posix_simple( const std::string & s )
120 {
121 return s.find('/') == std::string::npos ;
122 }
123 static void posix_normalise( std::string & s )
124 {
125 while( Str::replaceAll( s , "//" , "/" ) ) {;}
126 while( s.length() > 1U && s.at(s.length()-1U) == '/' ) s.resize(s.length()-1U) ;
127 }
128 static bool posix_absolute( const std::string & s )
129 {
130 return !s.empty() && s.at(0U) == '/' ;
131 }
132 static pos_t posix_rootsize( const std::string & s )
133 {
134 return s.empty() || s.at(0U) != '/' ? 0U : 1U ;
135 }
136 static std::string posix_null()
137 {
138 return "/dev/null" ;
139 }
140
141 static string_view sep()
142 {
143 return use_posix ? posix_sep() : windows_sep() ;
144 }
145 static void normalise( std::string & s )
146 {
147 use_posix ? posix_normalise(s) : windows_normalise(s) ;
148 }
149 static bool simple( const std::string & s )
150 {
151 return use_posix ? posix_simple(s) : windows_simple(s) ;
152 }
153 static bool absolute( const std::string & s )
154 {
155 return use_posix ? posix_absolute(s) : windows_absolute(s) ;
156 }
157 static std::string null()
158 {
159 return use_posix ? posix_null() : windows_null() ;
160 }
161 static pos_t rootsize( const std::string & s )
162 {
163 return use_posix ? posix_rootsize(s) : windows_rootsize(s) ;
164 }
165 static pos_t slashpos( const std::string & s )
166 {
167 return use_posix ? posix_slashpos(s) : windows_slashpos(s) ;
168 }
169 static pos_t dotpos( const std::string & s )
170 {
171 const pos_t npos = std::string::npos ;
172 const pos_t sp = slashpos( s ) ;
173 const pos_t dp = s.rfind( '.' ) ;
174 if( dp == npos )
175 return npos ;
176 else if( sp == npos )
177 return dp ;
178 else if( dp < sp )
179 return npos ;
180 else
181 return dp ;
182 }
183
184 static void splitInto( const std::string & str , StringArray & a )
185 {
186 pos_t rs = rootsize(str) ;
187 if( str.empty() )
188 {
189 }
190 else if( rs != 0U ) // ie. absolute or like "c:foo"
191 {
192 std::string root = str.substr( 0U , rs ) ;
193 Str::splitIntoTokens( Str::tail(str,rs-1U,std::string()) , a , sep() ) ;
194 a.insert( a.begin() , root ) ;
195 }
196 else
197 {
198 Str::splitIntoTokens( str , a , sep() ) ;
199 }
200 }
201
202 static bool purge( StringArray & a )
203 {
204 const std::string dot( 1U , '.' ) ;
205 a.erase( std::remove( a.begin() , a.end() , std::string() ) , a.end() ) ;
206 std::size_t n = a.size() ;
207 a.erase( std::remove( a.begin() , a.end() , dot ) , a.end() ) ;
208 const bool all_dots = a.empty() && n != 0U ;
209 return all_dots ;
210 }
211
212 static std::string join( StringArray::const_iterator p , StringArray::const_iterator end )
213 {
214 std::string str ;
215 int i = 0 ;
216 for( ; p != end ; ++p , i++ )
217 {
218 bool drive = !use_posix && str.length() == 2U && str.at(1U) == ':' ;
219 bool last_is_slash = !str.empty() &&
220 ( str.at(str.length()-1U) == '/' || str.at(str.length()-1U) == '\\' ) ;
221 if( i == 1 && (drive || last_is_slash) )
222 ;
223 else if( i != 0 )
224 str.append( sep().data() , sep().size() ) ;
225 str.append( *p ) ;
226 }
227 return str ;
228 }
229
230 static std::string join( const StringArray & a )
231 {
232 return join( a.begin() , a.end() ) ;
233 }
234
235 public:
236 PathImp() = delete ;
237} ;
238
239#ifdef G_WINDOWS
240 bool G::PathImp::use_posix = false ;
241#else
242 bool G::PathImp::use_posix = true ;
243#endif
244
245// ==
246
247 void G::Path::setPosixStyle()
248{
249 PathImp::use_posix = true ;
250}
251
252 void G::Path::setWindowsStyle()
253{
254 PathImp::use_posix = false ;
255}
256
257 G::Path::Path()
258= default;
259
260 G::Path::Path( const std::string & path ) :
261 m_str(path)
262{
263 PathImp::normalise( m_str ) ;
264}
265
266 G::Path::Path( const char * path ) :
267 m_str(path)
268{
269 PathImp::normalise( m_str ) ;
270}
271
272 G::Path::Path( const Path & path , const std::string & tail ) :
273 m_str(path.m_str)
274{
275 pathAppend( tail ) ;
276 PathImp::normalise( m_str ) ;
277}
278
279 G::Path::Path( const Path & path , const std::string & tail_1 , const std::string & tail_2 ) :
280 m_str(path.m_str)
281{
282 pathAppend( tail_1 ) ;
283 pathAppend( tail_2 ) ;
284 PathImp::normalise( m_str ) ;
285}
286
287 G::Path::Path( const Path & path , const std::string & tail_1 , const std::string & tail_2 ,
288 const std::string & tail_3 ) :
289 m_str(path.m_str)
290{
291 pathAppend( tail_1 ) ;
292 pathAppend( tail_2 ) ;
293 pathAppend( tail_3 ) ;
294 PathImp::normalise( m_str ) ;
295}
296
297 G::Path::Path( std::initializer_list<std::string> args )
298{
299 if( args.size() )
300 {
301 m_str = *args.begin() ;
302 for( const auto * p = args.begin()+1 ; p != args.end() ; ++p )
303 pathAppend( *p ) ;
304 PathImp::normalise( m_str ) ;
305 }
306}
307
308 G::Path G::Path::nullDevice()
309{
310 return Path( PathImp::null() ) ;
311}
312
313 bool G::Path::simple() const
314{
315 return dirname().empty() ;
316}
317
318 bool G::Path::isAbsolute() const
319{
320 return PathImp::absolute( m_str ) ;
321}
322
323 bool G::Path::isRelative() const
324{
325 return !isAbsolute() ;
326}
327
328std::string G::Path::basename() const
329{
330 StringArray a ;
331 PathImp::splitInto( m_str , a ) ;
332 PathImp::purge( a ) ;
333 return a.empty() ? std::string() : a.at(a.size()-1U) ;
334}
335
336 G::Path G::Path::dirname() const
337{
338 StringArray a ;
339 PathImp::splitInto( m_str , a ) ;
340 PathImp::purge( a ) ;
341 if( a.empty() ) return Path() ;
342 a.pop_back() ;
343 return join( a ) ;
344}
345
346 G::Path G::Path::withoutExtension() const
347{
348 std::string::size_type sp = PathImp::slashpos(m_str) ;
349 std::string::size_type dp = PathImp::dotpos(m_str) ;
350 if( dp != std::string::npos )
351 {
352 std::string result = m_str ;
353 result.resize( dp ) ;
354 if( (sp == std::string::npos && dp == 0U) || ((sp+1U) == dp) )
355 result.append(".") ; // special case
356 return Path( result ) ;
357 }
358 else
359 {
360 return *this ;
361 }
362}
363
364 G::Path G::Path::withExtension( const std::string & ext ) const
365{
366 std::string result = m_str ;
367 std::string::size_type dp = PathImp::dotpos(m_str) ;
368 if( dp != std::string::npos )
369 result.resize( dp ) ;
370 result.append( 1U , '.' ) ;
371 result.append( ext ) ;
372 return Path( result ) ;
373}
374
375 void G::Path::pathAppend( const std::string & tail )
376{
377 if( tail.empty() )
378 {
379 }
380 else if( PathImp::simple(tail) )
381 {
382 m_str.append( sv_to_string(PathImp::sep()) + tail ) ;
383 }
384 else
385 {
386 Path result = join( *this , tail ) ;
387 result.swap( *this ) ;
388 }
389}
390
391std::string G::Path::extension() const
392{
393 std::string::size_type pos = PathImp::dotpos(m_str) ;
394 return
395 pos == std::string::npos || (pos+1U) == m_str.length() ?
396 std::string() :
397 m_str.substr( pos+1U ) ;
398}
399
400 G::StringArray G::Path::split() const
401{
402 StringArray a ;
403 PathImp::splitInto( m_str , a ) ;
404 if( PathImp::purge(a) ) a.push_back( "." ) ;
405 return a ;
406}
407
408 G::Path G::Path::join( const G::StringArray & a )
409{
410 if( a.empty() ) return Path() ;
411 return Path( PathImp::join(a) ) ;
412}
413
414 G::Path G::Path::join( const G::Path & p1 , const G::Path & p2 )
415{
416 if( p1.empty() )
417 {
418 return p2 ;
419 }
420 else if( p2.empty() )
421 {
422 return p1 ;
423 }
424 else
425 {
426 StringArray a1 = p1.split() ;
427 StringArray a2 = p2.split() ;
428 a2.insert( a2.begin() , a1.begin() , a1.end() ) ;
429 return join( a2 ) ;
430 }
431}
432
433 G::Path G::Path::collapsed() const
434{
435 const std::string dots = ".." ;
436
437 StringArray a = split() ;
438 auto start = a.begin() ;
439 auto end = a.end() ;
440 if( start != end && isAbsolute() )
441 ++start ;
442
443 while( start != end )
444 {
445 while( start != end && *start == dots )
446 ++start ;
447
448 auto p_dots = std::find( start , end , dots ) ;
449 if( p_dots == end )
450 break ;
451
452 G_ASSERT( p_dots != a.begin() ) ;
453 G_ASSERT( a.size() >= 2U ) ;
454
455 a.erase( a.erase(--p_dots) ) ;
456 end = a.end() ;
457 }
458
459 return join( a ) ;
460}
461
462 bool G::Path::operator==( const Path & other ) const
463{
464 return m_str == other.m_str ; // noexcept only in c++14
465}
466
467 bool G::Path::operator!=( const Path & other ) const
468{
469 return m_str != other.m_str ; // noexcept only in c++14
470}
471
472 void G::Path::swap( Path & other ) noexcept
473{
474 using std::swap ;
475 swap( m_str , other.m_str ) ;
476}
477
478 static bool string_less( const std::string & a , const std::string & b )
479{
480 return a.compare(b) < 0 ; // uses std::char_traits<char>::compare()
481}
482
483 bool G::Path::less( const G::Path & a , const G::Path & b )
484{
485 StringArray a_parts = a.split() ;
486 StringArray b_parts = b.split() ;
487 return std::lexicographical_compare(
488 a_parts.begin() , a_parts.end() ,
489 b_parts.begin() , b_parts.end() ,
490 string_less ) ;
491}
492
493 G::Path G::Path::difference( const G::Path & root_in , const G::Path & path_in )
494{
495 StringArray path_parts ;
496 StringArray root_parts ;
497 if( !root_in.empty() ) root_parts = root_in.collapsed().split() ;
498 if( !path_in.empty() ) path_parts = path_in.collapsed().split() ;
499 if( root_parts.size() == 1U && root_parts.at(0U) == "." ) root_parts.clear() ;
500 if( path_parts.size() == 1U && path_parts.at(0U) == "." ) path_parts.clear() ;
501
502 if( path_parts.size() < root_parts.size() )
503 return Path() ;
504
505 using Pair = std::pair<StringArray::iterator,StringArray::iterator> ;
506 Pair p = std::mismatch( root_parts.begin() , root_parts.end() , path_parts.begin() ) ;
507
508 if( p.first == root_parts.end() && p.second == path_parts.end() )
509 return Path(".") ;
510 else if( p.first != root_parts.end() )
511 return Path() ;
512 else
513 return Path( PathImp::join(p.second,path_parts.end()) ) ;
514}
515
G::PathImp
A private implementation class used by G::Path providing a set of static methods.
Definition: gpath.cpp:36
G::Path
A Path object represents a file system path.
Definition: gpath.h:72
G::Path::swap
void swap(Path &other) noexcept
Swaps this with other.
Definition: gpath.cpp:472
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:483
G::Path::operator!=
bool operator!=(const Path &path) const
Comparison operator.
Definition: gpath.cpp:467
G::Path::isRelative
bool isRelative() const
Returns true if the path is a relative path or empty().
Definition: gpath.cpp:323
G::Path::join
static Path join(const StringArray &parts)
Builds a path from a set of parts.
Definition: gpath.cpp:408
G::Path::basename
std::string basename() const
Returns the rightmost part of the path, ignoring "." parts.
Definition: gpath.cpp:328
G::Path::withoutExtension
Path withoutExtension() const
Returns a path without the basename extension, if any.
Definition: gpath.cpp:346
G::Path::isAbsolute
bool isAbsolute() const
Returns !isRelative().
Definition: gpath.cpp:318
G::Path::dirname
Path dirname() const
Returns the path without the rightmost part, ignoring "." parts.
Definition: gpath.cpp:336
G::Path::extension
std::string extension() const
Returns the path's basename extension, ie.
Definition: gpath.cpp:391
G::Path::simple
bool simple() const
Returns true if the path has a single component (ignoring "." parts), ie.
Definition: gpath.cpp:313
G::Path::nullDevice
static Path nullDevice()
Returns the path of the "/dev/null" special file, or equivalent.
Definition: gpath.cpp:308
G::Path::pathAppend
void pathAppend(const std::string &tail)
Appends a filename or a relative path to this path.
Definition: gpath.cpp:375
G::Path::withExtension
Path withExtension(const std::string &ext) const
Returns the path with the new basename extension.
Definition: gpath.cpp:364
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:433
G::Path::setPosixStyle
static void setPosixStyle()
Sets posix mode for testing purposes.
Definition: gpath.cpp:247
G::Path::operator==
bool operator==(const Path &path) const
Comparison operator.
Definition: gpath.cpp:462
G::Path::setWindowsStyle
static void setWindowsStyle()
Sets windows mode for testing purposes.
Definition: gpath.cpp:252
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:400
G::Path::empty
bool empty() const noexcept
Returns true if size() is zero.
Definition: gpath.h:203
G::Path::Path
Path()
Default constructor for a zero-length path.
G::Path::difference
static Path difference(const Path &p1, const Path &p2)
Returns the relative path from p1 to p2.
Definition: gpath.cpp:493
G::Str::tail
static std::string tail(const std::string &in, std::size_t pos, const std::string &default_=std::string())
Returns the last part of the string after the given position.
Definition: gstr.cpp:1287
G::Str::splitIntoTokens
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:1073
G::Str::replaceAll
static unsigned int replaceAll(std::string &s, const std::string &from, const std::string &to)
Does a global replace on string 's', replacing all occurrences of sub-string 'from' with 'to'.
Definition: gstr.cpp:287
G::basic_string_view
A class template like c++17's std::basic_string_view.
Definition: gstringview.h:73
gassert.h
gpath.h
gstr.h
gstrings.h
gstringview.h
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:31