lean cpp library
A lean C++ library providing efficient utility classes for high-performance C++ applications.
filesystem.h
00001 /*****************************************************/
00002 /* lean I/O                     (c) Tobias Zirr 2011 */
00003 /*****************************************************/
00004 
00005 #ifndef LEAN_IO_FILESYSTEM
00006 #define LEAN_IO_FILESYSTEM
00007 
00008 #include "../lean.h"
00009 #include "../strings/types.h"
00010 #include "../strings/conversions.h"
00011 #include "../strings/string_traits.h"
00012 #include "../meta/strip.h"
00013 #include <algorithm>
00014 
00015 #ifndef LEAN_FILESYSTEM_PATH_LENGTH_HINT
00016 
00017 
00018     #define LEAN_FILESYSTEM_PATH_LENGTH_HINT 256
00019 #endif
00020 
00021 namespace lean
00022 {
00023 namespace io
00024 {
00025 
00027 LEAN_MAYBE_EXPORT bool file_exists(const utf16_nti& file);
00029 LEAN_INLINE bool file_exists(const utf8_ntri& file)
00030 {
00031     return file_exists(utf_to_utf16(file));
00032 }
00033 
00035 LEAN_MAYBE_EXPORT uint8 file_size(const utf16_nti& file);
00037 LEAN_INLINE uint8 file_size(const utf8_ntri& file)
00038 {
00039     return file_size(utf_to_utf16(file));
00040 }
00041 
00043 LEAN_MAYBE_EXPORT uint8 file_revision(const utf16_nti& file);
00045 LEAN_INLINE uint8 file_revision(const utf8_ntri& file)
00046 {
00047     return file_revision(utf_to_utf16(file));
00048 }
00049 
00053 LEAN_MAYBE_EXPORT size_t current_directory(utf16_t *buffer, size_t bufferSize);
00054 
00056 template <class String>
00057 String current_directory();
00058 
00059 #ifndef DOXYGEN_SKIP_THIS
00060 
00061 template <>
00062 inline utf16_string current_directory()
00063 {
00064     utf16_string result;
00065     
00066     result.resize(LEAN_FILESYSTEM_PATH_LENGTH_HINT);
00067     size_t actualLength = current_directory(&result[0], LEAN_FILESYSTEM_PATH_LENGTH_HINT);
00068     
00069     if (actualLength > LEAN_FILESYSTEM_PATH_LENGTH_HINT)
00070     {
00071         result.resize(actualLength);
00072         actualLength = current_directory(&result[0], actualLength);
00073     }
00074 
00075     result.erase(actualLength);
00076     return result;
00077 }
00078 
00079 template <>
00080 LEAN_INLINE utf8_string current_directory()
00081 {
00082     return strings::utf16_to_utf8<utf8_string>(current_directory<utf16_string>());
00083 }
00084 
00085 template <class String>
00086 LEAN_INLINE String current_directory()
00087 {
00088     return strings::utf_to_utf<String>(current_directory<utf16_string>());
00089 }
00090 
00091 #endif
00092 
00094 LEAN_INLINE utf8_string current_directory()
00095 {
00096     return strings::utf16_to_utf8<utf8_string>(current_directory<utf16_string>());
00097 }
00098 
00100 LEAN_INLINE const utf8_string& initial_directory()
00101 {
00102     static const utf8_string initialDir = current_directory<utf8_string>();
00103     return initialDir;
00104 }
00105 
00107 template <class Char>
00108 struct filesystem_chars
00109 {
00111     static const Char root_separator;
00113     static const Char path_separator;
00115     static const Char alt_path_separator;
00117     static const Char extension_separator;
00119     static const Char redirection;
00120 };
00121 
00122 template <class Char>
00123 const Char filesystem_chars<Char>::root_separator = ':';
00124 template <class Char>
00125 const Char filesystem_chars<Char>::path_separator = '/';
00126 template <class Char>
00127 const Char filesystem_chars<Char>::alt_path_separator = '\\';
00128 template <class Char>
00129 const Char filesystem_chars<Char>::extension_separator = '.';
00130 template <class Char>
00131 const Char filesystem_chars<Char>::redirection = '.';
00132 
00134 template <class Char>
00135 LEAN_INLINE bool is_root_separator(Char chr)
00136 {
00137     return (chr == filesystem_chars<Char>::root_separator);
00138 }
00139 
00141 template <class Char>
00142 LEAN_INLINE bool is_alt_path_separator(Char chr)
00143 {
00144     return (chr == filesystem_chars<Char>::alt_path_separator);
00145 }
00146 
00148 template <class Char>
00149 LEAN_INLINE bool is_path_separator(Char chr)
00150 {
00151     return (chr == filesystem_chars<Char>::path_separator) ||
00152         is_alt_path_separator(chr);
00153 }
00154 
00156 template <class Char>
00157 LEAN_INLINE bool is_extension_separator(Char chr)
00158 {
00159     return (chr == filesystem_chars<Char>::extension_separator);
00160 }
00161 
00163 template <class Char>
00164 LEAN_INLINE bool is_redirection(Char chr)
00165 {
00166     return (chr == filesystem_chars<Char>::redirection);
00167 }
00168 
00170 template <class Char>
00171 LEAN_INLINE Char& assign_path_separator(Char &chr)
00172 {
00173     return (chr = filesystem_chars<Char>::path_separator);
00174 }
00175 
00177 template <class Char>
00178 LEAN_INLINE Char& assign_extension_separator(Char &chr)
00179 {
00180     return (chr = filesystem_chars<Char>::extension_separator);
00181 }
00182 
00184 template <class Char>
00185 LEAN_INLINE Char& assign_redirection(Char &chr)
00186 {
00187     return (chr = filesystem_chars<Char>::redirection);
00188 }
00189 
00191 template <class Char>
00192 LEAN_INLINE Char& canonize_path_separator(Char &chr)
00193 {
00194     if (is_alt_path_separator(chr))
00195         assign_path_separator(chr);
00196     return chr;
00197 }
00198 
00201 template <class String, class Range1, class Range2>
00202 inline typename enable_if_range2<Range1, Range2, String>::type relative_path(const Range1 &base, const Range2 &path)
00203 {
00204     typedef string_traits<String> string_traits;
00205 
00206     typename Range1::const_iterator baseMarker = base.begin(),
00207         baseCursor = base.begin();
00208     typename Range2::const_iterator pathMarker = path.begin(),
00209         pathCursor = path.begin();
00210 
00211     bool endOfBase = (baseCursor == base.end()),
00212         endOfPath = (pathCursor == path.end());
00213     bool divergent = false;
00214 
00215     // Skip identical parts
00216     while (!divergent && !endOfBase && !endOfPath)
00217     {
00218         // Move forward to the end of the next directory name
00219         while (!endOfBase && !is_path_separator(*baseCursor))
00220             endOfBase = (++baseCursor == base.end());
00221         while (!endOfPath && !is_path_separator(*pathCursor))
00222             endOfPath = (++pathCursor == path.end());
00223 
00224         // Compare directory names
00225         divergent = (baseCursor - baseMarker != pathCursor - pathMarker)
00226             || !std::equal(baseMarker, baseCursor, pathMarker);
00227 
00228         // Skip path separator
00229         if (!endOfBase)
00230             endOfBase = (++baseCursor == base.end());
00231         if (!endOfPath)
00232             endOfPath = (++pathCursor == path.end());
00233 
00234         if (!divergent)
00235         {
00236             // Mark beginning of next directory name
00237             baseMarker = baseCursor;
00238             pathMarker = pathCursor;
00239         }
00240     }
00241 
00242     // ASSERT: paths equal up to their corresponding markers
00243 
00244     String result;
00245     string_traits::reserve(
00246             result,
00247             static_cast<typename string_traits::size_type>(base.end() - baseMarker)
00248             + static_cast<typename string_traits::size_type>(path.end() - pathMarker)
00249         );
00250 
00251     // Slash already skipped, don't lose corresponding directory
00252     int mismatchCount = (baseMarker != base.end()) ? 1 : 0;
00253     
00254     // Count mismatching base sub-directories
00255     for (baseCursor = baseMarker; baseCursor != base.end(); ++baseCursor)
00256         if (is_path_separator(*baseCursor))
00257             ++mismatchCount;
00258 
00259     if (mismatchCount != 0)
00260     {
00261         string_traits::resize(result, 3U * mismatchCount - 1U);
00262         typename string_traits::iterator insertCursor = string_traits::begin(result);
00263 
00264         // Add redirection for each mismatching sub-directory
00265         for (int i = 0; i < mismatchCount; ++i)
00266         {
00267             if (i != 0)
00268                 assign_path_separator(*(insertCursor++));
00269             assign_redirection(*(insertCursor++));
00270             assign_redirection(*(insertCursor++));
00271         }
00272     }
00273 
00274     // Append remaining sub-directories
00275     if (pathMarker != path.end())
00276     {
00277         bool needSeparation = !string_traits::empty(result);
00278         
00279         typename string_traits::size_type insertPos = string_traits::size(result);
00280         string_traits::resize(result, insertPos + static_cast<typename string_traits::size_type>(path.end() - pathMarker) + needSeparation);
00281         typename string_traits::iterator insertCursor = string_traits::begin(result) + insertPos;
00282         
00283         if (needSeparation)
00284             assign_path_separator(*(insertCursor++));
00285 
00286         insertCursor = std::copy(pathMarker, path.end(), insertCursor);
00287     }
00288 
00289     return result;
00290 }
00293 template <class String, class Chars1, class Chars2>
00294 LEAN_INLINE typename enable_if_not_range2<Chars1, Chars2, String>::type relative_path(const Chars1 &base, const Chars2 &path)
00295 {
00296     return relative_path<String>(make_char_range(base), make_char_range(path));
00297 }
00300 template <class Chars1, class Chars2>
00301 LEAN_INLINE std::basic_string<typename range_char_type2<Chars1, Chars2>::type> relative_path(const Chars1 &base, const Chars2 &path)
00302 {
00303     return relative_path< std::basic_string<typename range_char_type2<Chars1, Chars2>::type> >(make_char_range(base), make_char_range(path));
00304 }
00305 
00307 template <class String, class Range>
00308 inline typename enable_if_range<Range, String>::type canonical_path(const Range &path)
00309 {
00310     typedef string_traits<String> string_traits;
00311 
00312     String result;
00313     string_traits::resize(result, path.size());
00314 
00315     typename Range::const_iterator srcCursor = path.end(),
00316         srcMarker = path.end();
00317     typename string_traits::iterator destCursor = string_traits::end(result);
00318 
00319     int skipCounter = 0;
00320     int redirCounter = 0;
00321 
00322     bool endOfPath = (srcCursor == path.begin());
00323 
00324     while (!endOfPath)
00325     {
00326         // Pre-decrement, end never dereferenceable
00327         endOfPath = (--srcCursor == path.begin());
00328 
00329         bool isPathSeparator = is_path_separator(*srcCursor);
00330 
00331         if (is_redirection(*srcCursor))
00332         {
00333             // Count redirection characters, as long as no other characters around
00334             if (redirCounter != -1)
00335                 ++redirCounter;
00336         }
00337         else if (!isPathSeparator)
00338             // Not a redirection, treat as actual directory name
00339             redirCounter = -1;
00340 
00341         // End of current directory
00342         if (isPathSeparator || endOfPath)
00343         {
00344             typename Range::const_iterator srcInsertionCursor = srcCursor;
00345 
00346             // Don't copy leading path separators
00347             if (!endOfPath) // == isPathSeparator, except that leading unix-style root slashes are kept
00348                 ++srcInsertionCursor;
00349 
00350             // Ignore empty directory names & local directory
00351             if (redirCounter != 0 && redirCounter != 1)
00352             {
00353                 // Up one directory
00354                 if(redirCounter == 2)
00355                     // Skip this & also skip next actual directory name
00356                     ++skipCounter;
00357                 // Actual directory name
00358                 else if (skipCounter == 0)
00359                 {
00360                     // Prepend directory
00361                     typename string_traits::iterator newDestCursor = std::copy_backward(srcInsertionCursor, srcMarker, destCursor);
00362 
00363                     // Replace trailing & leading alternative path separators
00364                     canonize_path_separator(*(--destCursor));
00365                     canonize_path_separator(*newDestCursor);
00366 
00367                     destCursor = newDestCursor;
00368                 }
00369                 else
00370                     // Skip directory
00371                     --skipCounter;
00372             }
00373 
00374             // Remember insertion cursor, any leading path separator copied as trailing path separator next time
00375             srcMarker = srcInsertionCursor;
00376 
00377             redirCounter = 0;
00378         }
00379         
00380     }
00381 
00382     // Prepend unresolved redirections
00383     while (skipCounter != 0)
00384     {
00385         assign_path_separator(*(--destCursor));
00386         assign_redirection(*(--destCursor));
00387         assign_redirection(*(--destCursor));
00388         --skipCounter;
00389     }
00390 
00391     // ASSERT: size of canonical path <= size of source path
00392 
00393     // Move canonical path to front
00394     if (destCursor != string_traits::begin(result))
00395         string_traits::erase(result, string_traits::begin(result), destCursor);
00396 
00397     return result;
00398 }
00400 template <class String, class Chars>
00401 LEAN_INLINE typename enable_if_not_range<Chars, String>::type canonical_path(const Chars &path)
00402 {
00403     return canonical_path<String>(make_char_range(path));
00404 }
00406 template <class Chars>
00407 LEAN_INLINE std::basic_string<typename range_char_type<Chars>::type> canonical_path(const Chars &path)
00408 {
00409     return canonical_path< std::basic_string<typename range_char_type<Chars>::type> >(make_char_range(path));
00410 }
00411 
00413 template <class String, class Range1, class Range2>
00414 inline typename enable_if_range2<Range1, Range2, String>::type append_path(const Range1 &path, const Range2 &file)
00415 {
00416     typedef string_traits<String> string_traits;
00417 
00418     String result;
00419 
00420     string_traits::resize(result, path.size() + 1 + file.size());
00421     typename string_traits::iterator insertCursor = string_traits::begin(result);
00422 
00423     insertCursor = std::copy(path.begin(), path.end(), insertCursor);
00424 
00425     if (!path.empty() && !file.empty() &&
00426         !is_path_separator(*(path.end() - 1)) && !is_path_separator(*file.begin()))
00427         assign_path_separator(*(insertCursor++));
00428 
00429     insertCursor = std::copy(file.begin(), file.end(), insertCursor);
00430 
00431     string_traits::erase(result, insertCursor, result.end());
00432     return result;
00433 }
00435 template <class String, class Chars1, class Chars2>
00436 LEAN_INLINE typename enable_if_not_range2<Chars1, Chars2, String>::type append_path(const Chars1 &path, const Chars2 &file)
00437 {
00438     return append_path<String>(make_char_range(path), make_char_range(file));
00439 }
00441 template <class Chars1, class Chars2>
00442 LEAN_INLINE std::basic_string<typename range_char_type2<Chars1, Chars2>::type> append_path(const Chars1 &path, const Chars2 &file)
00443 {
00444     return append_path< std::basic_string<typename range_char_type2<Chars1, Chars2>::type> >(make_char_range(path), make_char_range(file));
00445 }
00446 
00448 template <class String, class Range1, class Range2>
00449 LEAN_INLINE String absolute_path(const Range1 &base, const Range2 &path)
00450 {
00451     return canonical_path<String>( append_path<String>(make_char_range(base), make_char_range(path)) );
00452 }
00454 template <class Chars1, class Chars2>
00455 LEAN_INLINE std::basic_string<typename range_char_type2<Chars1, Chars2>::type> absolute_path(const Chars1 &base, const Chars2 &path)
00456 {
00457     typedef std::basic_string<typename range_char_type2<Chars1, Chars2>::type> string_type;
00458     return canonical_path<string_type>( append_path<string_type>(make_char_range(base), make_char_range(path)) );
00459 }
00460 
00462 template <class Range>
00463 inline typename enable_if_range<Range, bool>::type is_rooted(const Range &path)
00464 {
00465     typename Range::const_iterator it = path.begin();
00466 
00467     if (it != path.end())
00468     {
00469         // Path separator at the beginning indicates root
00470         if (is_path_separator(*it))
00471             return true;
00472         
00473         // Root separator before first path separator indicates root
00474         while (++it != path.end())
00475             if (is_root_separator(*it))
00476                 return true;
00477             else if (is_path_separator(*it))
00478                 return false;
00479     }
00480 
00481     return false;
00482 }
00484 template <class Chars>
00485 LEAN_INLINE typename enable_if_not_range<Chars, bool>::type is_rooted(const Chars &path)
00486 {
00487     return is_rooted( make_char_range(path) );
00488 }
00489 
00491 template <class String, class Range>
00492 inline typename enable_if_range<Range, String>::type absolute_path(const Range &path)
00493 {
00494     String result;
00495 
00496     if (!is_rooted(path))
00497         result = append_path<String>(current_directory<String>(), make_char_range(path));
00498     else
00499         string_traits<String>::assign(result, path.begin(), path.end());
00500 
00501     return canonical_path<String>(result);
00502 }
00504 template <class String, class Chars>
00505 LEAN_INLINE typename enable_if_not_range<Chars, String>::type absolute_path(const Chars &path)
00506 {
00507     return absolute_path<String>( make_char_range(path) );
00508 }
00510 template <class Chars>
00511 LEAN_INLINE std::basic_string<typename range_char_type<Chars>::type> absolute_path(const Chars &path)
00512 {
00513     typedef std::basic_string<typename range_char_type<Chars>::type> string_type;
00514     return absolute_path<string_type>( make_char_range(path) );
00515 }
00516 
00518 template <class Iterator>
00519 inline Iterator get_directory(Iterator fileBegin, Iterator fileEnd)
00520 {
00521     Iterator dirEnd = fileEnd;
00522 
00523     while (dirEnd != fileBegin)
00524     {
00525         // Pre-decrement, end never dereferenceable
00526         --dirEnd;
00527 
00528         // Stop on first path separator, not included
00529         if (is_path_separator(*dirEnd))
00530             break;
00531     }
00532 
00533     return dirEnd;
00534 }
00536 template <class String, class Range>
00537 LEAN_INLINE String get_directory(const Range &file)
00538 {
00539     return string_traits<String>::construct(
00540             file.begin(),
00541             get_directory( file.begin(), file.end() )
00542         );
00543 }
00545 template <class String, class Char>
00546 LEAN_INLINE String get_directory(const Char *file)
00547 {
00548     return string_traits<String>::construct(
00549             file,
00550             get_directory( file, file + std::char_traits<Char>::length(file) )
00551         );
00552 }
00554 template <class Char>
00555 LEAN_INLINE std::basic_string<Char> get_directory(const Char *file)
00556 {
00557     return std::basic_string<Char>(
00558             file,
00559             get_directory( file, file + std::char_traits<Char>::length(file) )
00560         );
00561 }
00562 
00564 template <class Iterator>
00565 inline Iterator get_filename(Iterator fileBegin, Iterator fileEnd)
00566 {
00567     Iterator nameBegin = fileEnd;
00568 
00569     while (nameBegin != fileBegin)
00570     {
00571         // Pre-decrement, end never dereferenceable
00572         --nameBegin;
00573 
00574         // Stop on first path separator
00575         if (is_path_separator(*nameBegin))
00576         {
00577             // Separator not included
00578             ++nameBegin;
00579             break;
00580         }
00581     }
00582 
00583     return nameBegin;
00584 }
00586 template <class String, class Range>
00587 LEAN_INLINE String get_filename(const Range &file)
00588 {
00589     return string_traits<String>::construct(
00590             get_filename( file.begin(), file.end() ),
00591             file.end()
00592         );
00593 }
00595 template <class Char>
00596 LEAN_INLINE const Char* get_filename(const Char *file)
00597 {
00598     return get_filename(
00599             file,
00600             file + std::char_traits<Char>::length(file)
00601         );
00602 }
00603 
00605 template <class Iterator>
00606 inline range<Iterator> get_stem(Iterator fileBegin, Iterator fileEnd)
00607 {
00608     Iterator nameBegin = fileEnd, stemEnd = fileEnd;
00609 
00610     while (nameBegin != fileBegin)
00611     {
00612         // Pre-decrement, end never dereferenceable
00613         --nameBegin;
00614 
00615         // Stop on first path separator
00616         if (is_path_separator(*nameBegin))
00617         {
00618             // Separator not included
00619             ++nameBegin;
00620             break;
00621         }
00622         // Reset stem end on first extension separator
00623         else if (is_extension_separator(*nameBegin) && stemEnd == fileEnd)
00624             stemEnd = nameBegin;
00625     }
00626 
00627     return make_range(nameBegin, stemEnd);
00628 }
00630 template <class String, class Range>
00631 LEAN_INLINE String get_stem(const Range &file)
00632 {
00633     return string_from_range<String>(
00634             get_stem( file.begin(), file.end() )
00635         );
00636 }
00638 template <class String, class Char>
00639 LEAN_INLINE String get_stem(const Char *file)
00640 {
00641     return string_from_range<String>(
00642             get_stem( file, file + std::char_traits<Char>::length(file) )
00643         );
00644 }
00646 template <class Char>
00647 LEAN_INLINE std::basic_string<Char> get_stem(const Char *file)
00648 {
00649     return string_from_range< std::basic_string<Char> >(
00650             get_stem( file, file + std::char_traits<Char>::length(file) )
00651         );
00652 }
00653 
00655 template <class Iterator>
00656 inline Iterator get_extension(Iterator fileBegin, Iterator fileEnd)
00657 {
00658     Iterator extBegin = fileEnd;
00659 
00660     while (extBegin != fileBegin)
00661     {
00662         // Pre-decrement, end never dereferenceable
00663         --extBegin;
00664 
00665         // Stop on first extension or stop early on path separator
00666         if (is_extension_separator(*extBegin) || is_path_separator(*extBegin))
00667             break;
00668     }
00669 
00670     // Make sure return value is empty when no extension found
00671     if (extBegin != fileEnd && !is_extension_separator(*extBegin))
00672         extBegin = fileEnd;
00673 
00674     return extBegin;
00675 }
00677 template <class String, class Range>
00678 LEAN_INLINE String get_extension(const Range &file)
00679 {
00680     return string_traits<String>::construct(
00681             get_extension( file.begin(), file.end() ),
00682             file.end()
00683         );
00684 }
00686 template <class Char>
00687 LEAN_INLINE const Char* get_extension(const Char *file)
00688 {
00689     return get_extension( file, file + std::char_traits<Char>::length(file) );
00690 }
00691 
00692 } // namespace
00693 
00694 using io::file_exists;
00695 using io::file_revision;
00696 using io::file_size;
00697 
00698 using io::current_directory;
00699 using io::initial_directory;
00700 
00701 using io::relative_path;
00702 using io::absolute_path;
00703 using io::canonical_path;
00704 using io::append_path;
00705 
00706 using io::get_directory;
00707 using io::get_filename;
00708 using io::get_stem;
00709 using io::get_extension;
00710 
00711 } // namespace
00712 
00713 #ifdef LEAN_INCLUDE_LINKED
00714 #include "source/filesystem.cpp"
00715 #endif
00716 
00717 #endif