/* Copyright (C) 2001-2006, William Joseph. All Rights Reserved. This file is part of GtkRadiant. GtkRadiant is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GtkRadiant is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #if !defined ( INCLUDED_OS_PATH_H ) #define INCLUDED_OS_PATH_H #include "globaldefs.h" /// \file /// \brief OS file-system path comparison, decomposition and manipulation. /// /// - Paths are c-style null-terminated-character-arrays. /// - Path separators must be forward slashes (unix style). /// - Directory paths must end in a separator. /// - Paths must not contain the ascii characters \\ : * ? " < > or |. /// - Paths may be encoded in UTF-8 or any extended-ascii character set. #include "string/string.h" #if GDEF_OS_WINDOWS #define OS_CASE_INSENSITIVE #endif /// \brief Returns true if \p path is lexicographically sorted before \p other. /// If both \p path and \p other refer to the same file, neither will be sorted before the other. /// O(n) inline bool path_less( const char* path, const char* other ){ #if defined( OS_CASE_INSENSITIVE ) return string_less_nocase( path, other ); #else return string_less( path, other ); #endif } /// \brief Returns <0 if \p path is lexicographically less than \p other. /// Returns >0 if \p path is lexicographically greater than \p other. /// Returns 0 if both \p path and \p other refer to the same file. /// O(n) inline int path_compare( const char* path, const char* other ){ #if defined( OS_CASE_INSENSITIVE ) return string_compare_nocase( path, other ); #else return string_compare( path, other ); #endif } /// \brief Returns true if \p path and \p other refer to the same file or directory. /// O(n) inline bool path_equal( const char* path, const char* other ){ #if defined( OS_CASE_INSENSITIVE ) return string_equal_nocase( path, other ); #else return string_equal( path, other ); #endif } /// \brief Returns true if \p path and \p other refer to the same file or directory, case insensitively. /// O(n) inline bool path_equal_i( const char* path, const char* other ){ return string_equal_nocase( path, other ); } /// \brief Returns true if the first \p n bytes of \p path and \p other form paths that refer to the same file or directory. /// If the paths are UTF-8 encoded, [\p path, \p path + \p n) must be a complete path. /// O(n) inline bool path_equal_n( const char* path, const char* other, std::size_t n ){ #if defined( OS_CASE_INSENSITIVE ) return string_equal_nocase_n( path, other, n ); #else return string_equal_n( path, other, n ); #endif } /// \brief Returns true if \p path is a fully qualified file-system path. /// O(1) inline bool path_is_absolute( const char* path ){ #if GDEF_OS_WINDOWS return path[0] == '/' || ( path[0] != '\0' && path[1] == ':' ); // local drive #elif GDEF_OS_POSIX return path[0] == '/'; #endif } /// \brief Returns true if \p path is a directory. /// O(n) inline bool path_is_directory( const char* path ){ std::size_t length = strlen( path ); if ( length > 0 ) { return path[length - 1] == '/'; } return false; } /// \brief Returns a pointer to the first character of the component of \p path following the first directory component. /// O(n) inline const char* path_remove_directory( const char* path ){ const char* first_separator = strchr( path, '/' ); if ( first_separator != 0 ) { return ++first_separator; } return ""; } /// \brief Returns a pointer to the first character of the filename component of \p path. /// O(n) inline const char* path_get_filename_start( const char* path ){ { const char* last_forward_slash = strrchr( path, '/' ); if ( last_forward_slash != 0 ) { return last_forward_slash + 1; } } // not strictly necessary,since paths should not contain '\' { const char* last_backward_slash = strrchr( path, '\\' ); if ( last_backward_slash != 0 ) { return last_backward_slash + 1; } } return path; } /// \brief Returns a pointer to the character after the end of the filename component of \p path - either the extension separator or the terminating null character. /// O(n) inline const char* path_get_filename_base_end( const char* path ){ const char* last_period = strrchr( path_get_filename_start( path ), '.' ); return ( last_period != 0 ) ? last_period : path + string_length( path ); } /// \brief Returns the length of the filename component (not including extension) of \p path. /// O(n) inline std::size_t path_get_filename_base_length( const char* path ){ return path_get_filename_base_end( path ) - path; } /// \brief If \p path is a child of \p base, returns the subpath relative to \p base, else returns \p path. /// O(n) inline const char* path_make_relative( const char* path, const char* base ){ const std::size_t length = string_length( base ); if ( path_equal_n( path, base, length ) ) { return path + length; } return path; } /// \brief Returns a pointer to the first character of the file extension of \p path, or "" if not found. /// O(n) inline const char* path_get_extension( const char* path ){ const char* last_period = strrchr( path_get_filename_start( path ), '.' ); if ( last_period != 0 ) { return ++last_period; } return ""; } /// \brief Returns true if \p extension is of the same type as \p other. /// O(n) inline bool extension_equal( const char* extension, const char* other ){ return path_equal( extension, other ); } /// \brief Returns true if \p extension is of the same type as \p other, case insensitively. /// O(n) inline bool extension_equal_i( const char* extension, const char* other ){ return path_equal_i( extension, other ); } template class MatchFileExtension { const char* m_extension; const Functor& m_functor; public: MatchFileExtension( const char* extension, const Functor& functor ) : m_extension( extension ), m_functor( functor ){ } void operator()( const char* name ) const { const char* extension = path_get_extension( name ); if ( extension_equal( extension, m_extension ) ) { m_functor( name ); } } }; /// \brief A functor which invokes its contained \p functor if the \p name argument matches its \p extension. template inline MatchFileExtension matchFileExtension( const char* extension, const Functor& functor ){ return MatchFileExtension( extension, functor ); } class PathCleaned { public: const char* m_path; PathCleaned( const char* path ) : m_path( path ){ } }; /// \brief Writes \p path to \p ostream with dos-style separators replaced by unix-style separators. template TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const PathCleaned& path ){ const char* i = path.m_path; for (; *i != '\0'; ++i ) { if ( *i == '\\' ) { ostream << '/'; } else { ostream << *i; } } return ostream; } class DirectoryCleaned { public: const char* m_path; DirectoryCleaned( const char* path ) : m_path( path ){ } }; /// \brief Writes \p path to \p ostream with dos-style separators replaced by unix-style separators, and appends a separator if necessary. template TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const DirectoryCleaned& path ){ const char* i = path.m_path; for (; *i != '\0'; ++i ) { if ( *i == '\\' ) { ostream << '/'; } else { ostream << *i; } } char c = *( i - 1 ); if ( c != '/' && c != '\\' ) { ostream << '/'; } return ostream; } #endif