text: //  filesystem path.cpp  -------------------------------------------------------------  //
  
  //  Copyright Beman Dawes 2008
  
  //  Distributed under the Boost Software License, Version 1.0.
  //  See http://www.boost.org/LICENSE_1_0.txt
  
  //  Library home page: http://www.boost.org/libs/filesystem
  
  #include <boost/config.hpp>
  #if !defined( BOOST_NO_STD_WSTRING )
  // Boost.Filesystem V3 and later requires std::wstring support.
  // During the transition to V3, libraries are compiled with both V2 and V3 sources.
  // On old compilers that don't support V3 anyhow, we just skip everything so the compile
  // will succeed and the library can be built.
  
  // define BOOST_FILESYSTEM_SOURCE so that <boost/system/config.hpp> knows
  // the library is being built (possibly exporting rather than importing code)
  #define BOOST_FILESYSTEM_SOURCE 
  
  #ifndef BOOST_SYSTEM_NO_DEPRECATED 
  # define BOOST_SYSTEM_NO_DEPRECATED
  #endif
  
  #include <boost/filesystem/v3/config.hpp>
  #include <boost/filesystem/v3/path.hpp>
  #include <boost/scoped_array.hpp>
  #include <boost/system/error_code.hpp>
  #include <boost/assert.hpp>
  #include <cstddef>
  #include <cstring>
  #include <cassert>
  
  #ifdef BOOST_WINDOWS_API
  # include "windows_file_codecvt.hpp"
  # include <windows.h>
  #elif defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__)
  # include <boost/filesystem/detail/utf8_codecvt_facet.hpp>
  #endif
  
  #ifdef BOOST_FILESYSTEM_DEBUG
  # include <iostream>
  # include <iomanip>
  #endif
  
  namespace fs = boost::filesystem3;
  
  using boost::filesystem3::path;
  
  using std::string;
  using std::wstring;
  
  using boost::system::error_code;
  
  #ifndef BOOST_FILESYSTEM_CODECVT_BUF_SIZE
  # define BOOST_FILESYSTEM_CODECVT_BUF_SIZE 256
  #endif
  
  //--------------------------------------------------------------------------------------//
  //                                                                                      //
  //                                class path helpers                                    //
  //                                                                                      //
  //--------------------------------------------------------------------------------------//
  
  namespace
  {
    //------------------------------------------------------------------------------------//
    //                        miscellaneous class path helpers                            //
    //------------------------------------------------------------------------------------//
  
    typedef path::value_type        value_type;
    typedef path::string_type       string_type;
    typedef string_type::size_type  size_type;
  
    const std::size_t default_codecvt_buf_size = BOOST_FILESYSTEM_CODECVT_BUF_SIZE;
  
  # ifdef BOOST_WINDOWS_API
  
    const wchar_t separator = L'/';
    const wchar_t preferred_separator = L'\\';
    const wchar_t* const separators = L"/\\";
    const wchar_t* separator_string = L"/";
    const wchar_t* preferred_separator_string = L"\\";
    const wchar_t colon = L':';
    const wchar_t dot = L'.';
    const fs::path dot_path(L".");
    const fs::path dot_dot_path(L"..");
  
  # else
  
    const char separator = '/';
    const char preferred_separator = '/';
    const char* const separators = "/";
    const char* separator_string = "/";
    const char* preferred_separator_string = "/";
    const char colon = ':';
    const char dot = '.';
    const fs::path dot_path(".");
    const fs::path dot_dot_path("..");
  
  # endif
  
    inline bool is_separator(fs::path::value_type c)
    {
      return c == separator
  #     ifdef BOOST_WINDOWS_API
        || c == preferred_separator
  #     endif
        ;
    }
  
    bool is_non_root_separator(const string_type& str, size_type pos);
      // pos is position of the separator
  
    size_type filename_pos(const string_type& str,
                            size_type end_pos); // end_pos is past-the-end position
    //  Returns: 0 if str itself is filename (or empty)
  
    size_type root_directory_start(const string_type& path, size_type size);
    //  Returns:  npos if no root_directory found
  
    void first_element(
        const string_type& src,
        size_type& element_pos,
        size_type& element_size,
  #     if !BOOST_WORKAROUND(BOOST_MSVC, <= 1310) // VC++ 7.1
        size_type size = string_type::npos
  #     else
        size_type size = -1
  #     endif
      );
  
  }  // unnamed namespace
  
  //--------------------------------------------------------------------------------------//
  //                                                                                      //
  //                            class path implementation                                 //
  //                                                                                      //
  //--------------------------------------------------------------------------------------//
  
  namespace boost
  {
  namespace filesystem3
  {
  
    path & path::operator/=(const path & p)
    {
      if (p.empty())
        return *this;
      if (!is_separator(*p.m_pathname.begin()))
        m_append_separator_if_needed();
      m_pathname += p.m_pathname;
      return *this;
    }
  
  # ifdef BOOST_WINDOWS_API
  
    void path::m_portable()
    {
      for (string_type::iterator it = m_pathname.begin();
            it != m_pathname.end(); ++it)
      {
        if (*it == L'\\')
          *it = L'/';
      }
    }
  
    const std::string path::generic_string(const codecvt_type& cvt) const
    { 
      path tmp(*this);
      tmp.m_portable();
      return tmp.string(cvt);
    }
  
    const std::wstring path::generic_wstring() const
    { 
      path tmp(*this);
      tmp.m_portable();
      return tmp.wstring();
    }
  
  # endif  // BOOST_WINDOWS_API
  
    //  m_append_separator_if_needed  ----------------------------------------------------//
  
    path::string_type::size_type path::m_append_separator_if_needed()
    {
      if (!m_pathname.empty() &&
  #     ifdef BOOST_WINDOWS_API
        *(m_pathname.end()-1) != colon && 
  #     endif
        !is_separator(*(m_pathname.end()-1)))
      {
        string_type::size_type tmp(m_pathname.size());
        m_pathname += preferred_separator;
        return tmp;
      }
      return 0;
    }
  
    //  m_erase_redundant_separator  -----------------------------------------------------//
  
    void path::m_erase_redundant_separator(string_type::size_type sep_pos)
    {
      if (sep_pos                         // a separator was added
        && sep_pos < m_pathname.size()         // and something was appended
        && (m_pathname[sep_pos+1] == separator // and it was also separator
  #      ifdef BOOST_WINDOWS_API
         || m_pathname[sep_pos+1] == preferred_separator  // or preferred_separator
  #      endif
  )) { m_pathname.erase(sep_pos, 1); } // erase the added separator
    }
  
    //  modifiers  -----------------------------------------------------------------------//
  
  # ifdef BOOST_WINDOWS_API
    path & path::make_preferred()
    {
      for (string_type::iterator it = m_pathname.begin();
            it != m_pathname.end(); ++it)
      {
        if (*it == L'/')
          *it = L'\\';
      }
      return *this;
    }
  # endif
  
    path& path::remove_filename()
    {
      m_pathname.erase(m_parent_path_end());
      return *this;
    }
  
    path & path::replace_extension(const path & source)
    {
      // erase existing extension if any
      size_type pos(m_pathname.rfind(dot));
      if (pos != string_type::npos && pos >= filename_pos(m_pathname, m_pathname.size()))
        m_pathname.erase(pos);
  
      // append source extension if any
      pos = source.m_pathname.rfind(dot);
      if (pos != string_type::npos)
        m_pathname += source.c_str() + pos;
  
      return *this;
    }
  
    //  decomposition  -------------------------------------------------------------------//
  
    path  path::root_path() const
    { 
      path temp(root_name());
      if (!root_directory().empty()) temp.m_pathname += root_directory().c_str();
      return temp;
    } 
  
    path path::root_name() const
    {
      iterator itr(begin());
  
      return (itr.m_pos != m_pathname.size()
        && (
            (itr.m_element.m_pathname.size() > 1
              && is_separator(itr.m_element.m_pathname[0])
              && is_separator(itr.m_element.m_pathname[1])
     )
  #       ifdef BOOST_WINDOWS_API
          || itr.m_element.m_pathname[itr.m_element.m_pathname.size()-1] == colon
  #       endif
    ))
        ? itr.m_element
        : path();
    }
  
    path path::root_directory() const
    {
      size_type pos(root_directory_start(m_pathname, m_pathname.size()));
  
      return pos == string_type::npos
        ? path()
        : path(m_pathname.c_str() + pos, m_pathname.c_str() + pos + 1);
    }
  
    path path::relative_path() const
    {
      iterator itr(begin());
  
      for (; itr.m_pos != m_pathname.size()
        && (is_separator(itr.m_element.m_pathname[0])
  #     ifdef BOOST_WINDOWS_API
        || itr.m_element.m_pathname[itr.m_element.m_pathname.size()-1] == colon
  #     endif
      ); ++itr) {}
  
      return path(m_pathname.c_str() + itr.m_pos);
    }
  
    string_type::size_type path::m_parent_path_end() const
    {
      size_type end_pos(filename_pos(m_pathname, m_pathname.size()));
  
      bool filename_was_separator(m_pathname.size()
        && is_separator(m_pathname[end_pos]));
  
      // skip separators unless root directory
      size_type root_dir_pos(root_directory_start(m_pathname, end_pos));
      for (; 
        end_pos > 0
        && (end_pos-1) != root_dir_pos
        && is_separator(m_pathname[end_pos-1])
        ;
        --end_pos) {}
  
     return (end_pos == 1 && root_dir_pos == 0 && filename_was_separator)
       ? string_type::npos
       : end_pos;
    }
  
    path path::parent_path() const
    {
     size_type end_pos(m_parent_path_end());
     return end_pos == string_type::npos
       ? path()
       : path(m_pathname.c_str(), m_pathname.c_str() + end_pos);
    }
  
    path path::filename() const
    {
      size_type pos(filename_pos(m_pathname, m_pathname.size()));
      return (m_pathname.size()
                && pos
                && is_separator(m_pathname[pos])
                && is_non_root_separator(m_pathname, pos))
        ? dot_path
        : path(m_pathname.c_str() + pos);
    }
  
    path path::stem() const
    {
      path name(filename());
      if (name == dot_path || name == dot_dot_path) return name;
      size_type pos(name.m_pathname.rfind(dot));
      return pos == string_type::npos
        ? name
        : path(name.m_pathname.c_str(), name.m_pathname.c_str() + pos);
    }
  
    path path::extension() const
    {
      path name(filename());
      if (name == dot_path || name == dot_dot_path) return path();
      size_type pos(name.m_pathname.rfind(dot));
      return pos == string_type::npos
        ? path()
        : path(name.m_pathname.c_str() + pos);
    }
  
    // m_normalize  ----------------------------------------------------------------------//
  
    path& path::m_normalize()
    {
      if (m_pathname.empty()) return *this;
        
      path temp;
      iterator start(begin());
      iterator last(end());
      iterator stop(last--);
      for (iterator itr(start); itr != stop; ++itr)
      {
        // ignore "." except at start and last
        if (itr->native().size() == 1
          && (itr->native())[0] == dot
          && itr != start
          && itr != last) continue;
  
        // ignore a name and following ".."
        if (!temp.empty()
          && itr->native().size() == 2
          && (itr->native())[0] == dot
          && (itr->native())[1] == dot) // dot dot
        {
          string_type lf(temp.filename().native());  
          if (lf.size() > 0  
            && (lf.size() != 1
              || (lf[0] != dot
                && lf[0] != separator))
            && (lf.size() != 2 
              || (lf[0] != dot
                && lf[1] != dot
  #             ifdef BOOST_WINDOWS_API
                && lf[1] != colon
  #             endif
                 )
               )
            )
          {
            temp.remove_filename();
            // if not root directory, must also remove "/" if any
            if (temp.m_pathname.size() > 0
              && temp.m_pathname[temp.m_pathname.size()-1]
                == separator)
            {
              string_type::size_type rds(
                root_directory_start(temp.m_pathname, temp.m_pathname.size()));
              if (rds == string_type::npos
                || rds != temp.m_pathname.size()-1) 
                { temp.m_pathname.erase(temp.m_pathname.size()-1); }
            }
  
            iterator next(itr);
            if (temp.empty() && ++next != stop
              && next == last && *last == dot_path) temp /= dot_path;
            continue;
          }
        }
  
        temp /= *itr;
      };
  
      if (temp.empty()) temp /= dot_path;
      m_pathname = temp.m_pathname;
      return *this;
    }
  
  }  // namespace filesystem3
  }  // namespace boost
    
  //--------------------------------------------------------------------------------------//
  //                                                                                      //
  //                         class path helpers implementation                            //
  //                                                                                      //
  //--------------------------------------------------------------------------------------//
  
  namespace
  {
  
    //  is_non_root_separator  -------------------------------------------------//
  
    bool is_non_root_separator(const string_type & str, size_type pos)
      // pos is position of the separator
    {
      BOOST_ASSERT(!str.empty() && is_separator(str[pos])
        && "precondition violation");
  
      // subsequent logic expects pos to be for leftmost slash of a set
      while (pos > 0 && is_separator(str[pos-1]))
        --pos;
  
      return  pos != 0
        && (pos <= 2 || !is_separator(str[1])
          || str.find_first_of(separators, 2) != pos)
  #     ifdef BOOST_WINDOWS_API
        && (pos !=2 || str[1] != colon)
  #     endif
          ;
    }
  
    //  filename_pos  --------------------------------------------------------------------//
  
    size_type filename_pos(const string_type & str,
                            size_type end_pos) // end_pos is past-the-end position
      // return 0 if str itself is filename (or empty)
    {
      // case: "//"
      if (end_pos == 2 
        && is_separator(str[0])
        && is_separator(str[1])) return 0;
  
      // case: ends in "/"
      if (end_pos && is_separator(str[end_pos-1]))
        return end_pos-1;
      
      // set pos to start of last element
      size_type pos(str.find_last_of(separators, end_pos-1));
  
  #   ifdef BOOST_WINDOWS_API
      if (pos == string_type::npos)
        pos = str.find_last_of(colon, end_pos-2);
  #   endif
  
      return (pos == string_type::npos // path itself must be a filename (or empty)
        || (pos == 1 && is_separator(str[0]))) // or net
          ? 0 // so filename is entire string
          : pos + 1; // or starts after delimiter
    }
  
    //  root_directory_start  ------------------------------------------------------------//
  
    size_type root_directory_start(const string_type & path, size_type size)
    // return npos if no root_directory found
    {
  
  #   ifdef BOOST_WINDOWS_API
      // case "c:/"
      if (size > 2
        && path[1] == colon
        && is_separator(path[2])) return 2;
  #   endif
  
      // case "//"
      if (size == 2
        && is_separator(path[0])
        && is_separator(path[1])) return string_type::npos;
  
      // case "//net {/}"
      if (size > 3
        && is_separator(path[0])
        && is_separator(path[1])
        && !is_separator(path[2]))
      {
        string_type::size_type pos(path.find_first_of(separators, 2));
        return pos < size ? pos : string_type::npos;
      }
      
      // case "/"
      if (size > 0 && is_separator(path[0])) return 0;
  
      return string_type::npos;
    }
  
    //  first_element --------------------------------------------------------------------//
    //   sets pos and len of first element, excluding extra separators
    //   if src.empty(), sets pos,len, to 0,0.
  
    void first_element(
        const string_type & src,
        size_type & element_pos,
        size_type & element_size,
        size_type size
  )
    {
      if (size == string_type::npos) size = src.size();
      element_pos = 0;
      element_size = 0;
      if (src.empty()) return;
  
      string_type::size_type cur(0);
      
      // deal with // [network]
      if (size >= 2 && is_separator(src[0])
        && is_separator(src[1])
        && (size == 2
          || !is_separator(src[2])))
      { 
        cur += 2;
        element_size += 2;
      }
  
      // leading (not non-network) separator
      else if (is_separator(src[0]))
      {
        ++element_size;
        // bypass extra leading separators
        while (cur+1 < size
          && is_separator(src[cur+1]))
        {
          ++cur;
          ++element_pos;
        }
        return;
      }
  
      // at this point, we have either a plain name, a network name,
      // or (on Windows only) a device name
  
      // find the end
      while (cur < size
  #     ifdef BOOST_WINDOWS_API
        && src[cur] != colon
  #     endif
        && !is_separator(src[cur]))
      {
        ++cur;
        ++element_size;
      }
  
  #   ifdef BOOST_WINDOWS_API
      if (cur == size) return;
      // include device delimiter
      if (src[cur] == colon)
        { ++element_size; }
  #   endif
  
      return;
    }
  
  }  // unnammed namespace
  
  //--------------------------------------------------------------------------------------//
  //                                                                                      //
  //                        class path::iterator implementation                           //
  //                                                                                      //
  //--------------------------------------------------------------------------------------//
  
  namespace boost
  {
  namespace filesystem3
  {
  
    path::iterator path::begin() const
    {
      iterator itr;
      itr.m_path_ptr = this;
      size_type element_size;
      first_element(m_pathname, itr.m_pos, element_size);
      itr.m_element = m_pathname.substr(itr.m_pos, element_size);
      if (itr.m_element.m_pathname == preferred_separator_string)
        itr.m_element.m_pathname = separator_string;  // needed for Windows, harmless on POSIX
      return itr;
    }
  
    path::iterator path::end() const
    {
      iterator itr;
      itr.m_path_ptr = this;
      itr.m_pos = m_pathname.size();
      return itr;
    }
  
    void path::m_path_iterator_increment(path::iterator & it)
    {
      BOOST_ASSERT(it.m_pos < it.m_path_ptr->m_pathname.size() && "path::basic_iterator increment past end()");
  
      // increment to position past current element
      it.m_pos += it.m_element.m_pathname.size();
  
      // if end reached, create end basic_iterator
      if (it.m_pos == it.m_path_ptr->m_pathname.size())
      {
        it.m_element.clear(); 
        return;
      }
  
      // both POSIX and Windows treat paths that begin with exactly two separators specially
      bool was_net(it.m_element.m_pathname.size() > 2
        && is_separator(it.m_element.m_pathname[0])
        && is_separator(it.m_element.m_pathname[1])
        && !is_separator(it.m_element.m_pathname[2]));
  
      // process separator (Windows drive spec is only case not a separator)
      if (is_separator(it.m_path_ptr->m_pathname[it.m_pos]))
      {
        // detect root directory
        if (was_net
  #       ifdef BOOST_WINDOWS_API
          // case "c:/"
          || it.m_element.m_pathname[it.m_element.m_pathname.size()-1] == colon
  #       endif
           )
        {
          it.m_element.m_pathname = separator;
          return;
        }
  
        // bypass separators
        while (it.m_pos != it.m_path_ptr->m_pathname.size()
          && is_separator(it.m_path_ptr->m_pathname[it.m_pos]))
          { ++it.m_pos; }
  
        // detect trailing separator, and treat it as ".", per POSIX spec
        if (it.m_pos == it.m_path_ptr->m_pathname.size()
          && is_non_root_separator(it.m_path_ptr->m_pathname, it.m_pos-1)) 
        {
          --it.m_pos;
          it.m_element = dot_path;
          return;
        }
      }
  
      // get next element
      size_type end_pos(it.m_path_ptr->m_pathname.find_first_of(separators, it.m_pos));
      if (end_pos == string_type::npos) end_pos = it.m_path_ptr->m_pathname.size();
      it.m_element = it.m_path_ptr->m_pathname.substr(it.m_pos, end_pos - it.m_pos);
    }
  
    void path::m_path_iterator_decrement(path::iterator & it)
    {
      BOOST_ASSERT(it.m_pos && "path::iterator decrement past begin()");
  
      size_type end_pos(it.m_pos);
  
      // if at end and there was a trailing non-root '/', return "."
      if (it.m_pos == it.m_path_ptr->m_pathname.size()
        && it.m_path_ptr->m_pathname.size() > 1
        && is_separator(it.m_path_ptr->m_pathname[it.m_pos-1])
        && is_non_root_separator(it.m_path_ptr->m_pathname, it.m_pos-1) 
         )
      {
        --it.m_pos;
        it.m_element = dot_path;
        return;
      }
  
      size_type root_dir_pos(root_directory_start(it.m_path_ptr->m_pathname, end_pos));
  
      // skip separators unless root directory
      for (
        ; 
        end_pos > 0
        && (end_pos-1) != root_dir_pos
        && is_separator(it.m_path_ptr->m_pathname[end_pos-1])
        ;
        --end_pos) {}
  
      it.m_pos = filename_pos(it.m_path_ptr->m_pathname, end_pos);
      it.m_element = it.m_path_ptr->m_pathname.substr(it.m_pos, end_pos - it.m_pos);
      if (it.m_element.m_pathname == preferred_separator_string)
        it.m_element.m_pathname = separator_string;  // needed for Windows, harmless on POSIX
    }
  
  }  // namespace filesystem3
  }  // namespace boost
  
  //--------------------------------------------------------------------------------------//
  //                                                                                      //
  //                                 detail helpers                                       //
  //                                                                                      //
  //--------------------------------------------------------------------------------------//
  
  namespace
  {
  
    //------------------------------------------------------------------------------------//
    //                              locale helpers                                        //
    //------------------------------------------------------------------------------------//
  
    // std::locale construction can throw (if LC_MESSAGES is wrong, for example),
    // so a static at function scope is used to ensure that exceptions can be
    // caught. (A previous version was at namespace scope, so initialization
    // occurred before main(), preventing exceptions from being caught.)
  
    std::locale default_locale()
    {
  #   ifdef BOOST_WINDOWS_API
      std::locale global_loc = std::locale();
      std::locale loc(global_loc, new windows_file_codecvt);
      return loc;
  
  #   elif defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__)
      // "All BSD system functions expect their string parameters to be in UTF-8 encoding
      // and nothing else." http://developer.apple.com/mac/library/documentation/MacOSX/Conceptual/BPInternational/Articles/FileEncodings.html
      //
      // "The kernel will reject any filename that is not a valid UTF-8 string, and it will
      // even be normalized (to Unicode NFD) before stored on disk, at least when using HFS.
      // The right way to deal with it would be to always convert the filename to UTF-8
      // before trying to open/create a file." http://lists.apple.com/archives/unix-porting/2007/Sep/msg00023.html
      //
      // "How a file name looks at the API level depends on the API. Current Carbon APIs
      // handle file names as an array of UTF-16 characters; POSIX ones handle them as an
      // array of UTF-8, which is why UTF-8 works well in Terminal. How it's stored on disk
      // depends on the disk format; HFS+ uses UTF-16, but that's not important in most
      // cases." http://lists.apple.com/archives/applescript-users/2002/Sep/msg00319.html
      //
      // Many thanks to Peter Dimov for digging out the above references!
      std::locale global_loc = std::locale();
      std::locale loc(global_loc, new boost::filesystem::detail::utf8_codecvt_facet);
      return loc;
  
  #   else
      // ISO C calls this "the locale-specific native environment":
      return std::locale("");
  
  #   endif
    }
  
    std::locale & path_locale()
    {
      static std::locale loc(default_locale());
      return loc;
    }
  
  }  // unnamed namespace
  
  //--------------------------------------------------------------------------------------//
  //                           path::imbue implementation                                 //
  //--------------------------------------------------------------------------------------//
  
  namespace boost
  {
  namespace filesystem3
  {
  
    const path::codecvt_type *&
      path::wchar_t_codecvt_facet()
    {
     static const std::codecvt<wchar_t, char, std::mbstate_t> *
       facet(
         &std::use_facet<std::codecvt<wchar_t, char, std::mbstate_t> >
          (path_locale()));
     return facet;
    }
  
    std::locale path::imbue(const std::locale & loc)
    {
      std::locale temp(path_locale());
      path_locale() = loc;
      wchar_t_codecvt_facet() = &std::use_facet
          <std::codecvt<wchar_t, char, std::mbstate_t> >(path_locale());
      return temp;
    }
  
  }  // namespace filesystem3
  }  // namespace boost
  
  #endif  // no wide character support
  
  