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