]> git.xonotic.org Git - xonotic/netradiant.git/blob - libs/os/path.h
radiant/texwindow: test .wad file extension case-insensitively on all systems, fix...
[xonotic/netradiant.git] / libs / os / path.h
1 /*
2    Copyright (C) 2001-2006, William Joseph.
3    All Rights Reserved.
4
5    This file is part of GtkRadiant.
6
7    GtkRadiant is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    GtkRadiant is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with GtkRadiant; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21
22 #if !defined ( INCLUDED_OS_PATH_H )
23 #define INCLUDED_OS_PATH_H
24
25 #include "globaldefs.h"
26
27 /// \file
28 /// \brief OS file-system path comparison, decomposition and manipulation.
29 ///
30 /// - Paths are c-style null-terminated-character-arrays.
31 /// - Path separators must be forward slashes (unix style).
32 /// - Directory paths must end in a separator.
33 /// - Paths must not contain the ascii characters \\ : * ? " < > or |.
34 /// - Paths may be encoded in UTF-8 or any extended-ascii character set.
35
36 #include "string/string.h"
37
38 #if GDEF_OS_WINDOWS
39 #define OS_CASE_INSENSITIVE
40 #endif
41
42 /// \brief Returns true if \p path is lexicographically sorted before \p other.
43 /// If both \p path and \p other refer to the same file, neither will be sorted before the other.
44 /// O(n)
45 inline bool path_less( const char* path, const char* other ){
46 #if defined( OS_CASE_INSENSITIVE )
47         return string_less_nocase( path, other );
48 #else
49         return string_less( path, other );
50 #endif
51 }
52
53 /// \brief Returns <0 if \p path is lexicographically less than \p other.
54 /// Returns >0 if \p path is lexicographically greater than \p other.
55 /// Returns 0 if both \p path and \p other refer to the same file.
56 /// O(n)
57 inline int path_compare( const char* path, const char* other ){
58 #if defined( OS_CASE_INSENSITIVE )
59         return string_compare_nocase( path, other );
60 #else
61         return string_compare( path, other );
62 #endif
63 }
64
65 /// \brief Returns true if \p path and \p other refer to the same file or directory.
66 /// O(n)
67 inline bool path_equal( const char* path, const char* other ){
68 #if defined( OS_CASE_INSENSITIVE )
69         return string_equal_nocase( path, other );
70 #else
71         return string_equal( path, other );
72 #endif
73 }
74
75 /// \brief Returns true if \p path and \p other refer to the same file or directory, case insensitively.
76 /// O(n)
77 inline bool path_equal_i( const char* path, const char* other ){
78         return string_equal_nocase( path, other );
79 }
80
81 /// \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.
82 /// If the paths are UTF-8 encoded, [\p path, \p path + \p n) must be a complete path.
83 /// O(n)
84 inline bool path_equal_n( const char* path, const char* other, std::size_t n ){
85 #if defined( OS_CASE_INSENSITIVE )
86         return string_equal_nocase_n( path, other, n );
87 #else
88         return string_equal_n( path, other, n );
89 #endif
90 }
91
92
93 /// \brief Returns true if \p path is a fully qualified file-system path.
94 /// O(1)
95 inline bool path_is_absolute( const char* path ){
96 #if GDEF_OS_WINDOWS
97         return path[0] == '/'
98                    || ( path[0] != '\0' && path[1] == ':' ); // local drive
99 #elif GDEF_OS_POSIX
100         return path[0] == '/';
101 #endif
102 }
103
104 /// \brief Returns true if \p path is a directory.
105 /// O(n)
106 inline bool path_is_directory( const char* path ){
107         std::size_t length = strlen( path );
108         if ( length > 0 ) {
109                 return path[length - 1] == '/';
110         }
111         return false;
112 }
113
114 /// \brief Returns a pointer to the first character of the component of \p path following the first directory component.
115 /// O(n)
116 inline const char* path_remove_directory( const char* path ){
117         const char* first_separator = strchr( path, '/' );
118         if ( first_separator != 0 ) {
119                 return ++first_separator;
120         }
121         return "";
122 }
123
124 /// \brief Returns a pointer to the first character of the filename component of \p path.
125 /// O(n)
126 inline const char* path_get_filename_start( const char* path ){
127         {
128                 const char* last_forward_slash = strrchr( path, '/' );
129                 if ( last_forward_slash != 0 ) {
130                         return last_forward_slash + 1;
131                 }
132         }
133
134         // not strictly necessary,since paths should not contain '\'
135         {
136                 const char* last_backward_slash = strrchr( path, '\\' );
137                 if ( last_backward_slash != 0 ) {
138                         return last_backward_slash + 1;
139                 }
140         }
141
142         return path;
143 }
144
145 /// \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.
146 /// O(n)
147 inline const char* path_get_filename_base_end( const char* path ){
148         const char* last_period = strrchr( path_get_filename_start( path ), '.' );
149         return ( last_period != 0 ) ? last_period : path + string_length( path );
150 }
151
152 /// \brief Returns the length of the filename component (not including extension) of \p path.
153 /// O(n)
154 inline std::size_t path_get_filename_base_length( const char* path ){
155         return path_get_filename_base_end( path ) - path;
156 }
157
158 /// \brief If \p path is a child of \p base, returns the subpath relative to \p base, else returns \p path.
159 /// O(n)
160 inline const char* path_make_relative( const char* path, const char* base ){
161         const std::size_t length = string_length( base );
162         if ( path_equal_n( path, base, length ) ) {
163                 return path + length;
164         }
165         return path;
166 }
167
168 /// \brief Returns a pointer to the first character of the file extension of \p path, or "" if not found.
169 /// O(n)
170 inline const char* path_get_extension( const char* path ){
171         const char* last_period = strrchr( path_get_filename_start( path ), '.' );
172         if ( last_period != 0 ) {
173                 return ++last_period;
174         }
175         return "";
176 }
177
178 /// \brief Returns true if \p extension is of the same type as \p other.
179 /// O(n)
180 inline bool extension_equal( const char* extension, const char* other ){
181         return path_equal( extension, other );
182 }
183
184 /// \brief Returns true if \p extension is of the same type as \p other, case insensitively.
185 /// O(n)
186 inline bool extension_equal_i( const char* extension, const char* other ){
187         return path_equal_i( extension, other );
188 }
189
190 template<typename Functor>
191 class MatchFileExtension
192 {
193 const char* m_extension;
194 const Functor& m_functor;
195 public:
196 MatchFileExtension( const char* extension, const Functor& functor ) : m_extension( extension ), m_functor( functor ){
197 }
198 void operator()( const char* name ) const {
199         const char* extension = path_get_extension( name );
200         if ( extension_equal( extension, m_extension ) ) {
201                 m_functor( name );
202         }
203 }
204 };
205
206 /// \brief A functor which invokes its contained \p functor if the \p name argument matches its \p extension.
207 template<typename Functor>
208 inline MatchFileExtension<Functor> matchFileExtension( const char* extension, const Functor& functor ){
209         return MatchFileExtension<Functor>( extension, functor );
210 }
211
212 class PathCleaned
213 {
214 public:
215 const char* m_path;
216 PathCleaned( const char* path ) : m_path( path ){
217 }
218 };
219
220 /// \brief Writes \p path to \p ostream with dos-style separators replaced by unix-style separators.
221 template<typename TextOutputStreamType>
222 TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const PathCleaned& path ){
223         const char* i = path.m_path;
224         for (; *i != '\0'; ++i )
225         {
226                 if ( *i == '\\' ) {
227                         ostream << '/';
228                 }
229                 else
230                 {
231                         ostream << *i;
232                 }
233         }
234         return ostream;
235 }
236
237 class DirectoryCleaned
238 {
239 public:
240 const char* m_path;
241 DirectoryCleaned( const char* path ) : m_path( path ){
242 }
243 };
244
245 /// \brief Writes \p path to \p ostream with dos-style separators replaced by unix-style separators, and appends a separator if necessary.
246 template<typename TextOutputStreamType>
247 TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const DirectoryCleaned& path ){
248         const char* i = path.m_path;
249         for (; *i != '\0'; ++i )
250         {
251                 if ( *i == '\\' ) {
252                         ostream << '/';
253                 }
254                 else
255                 {
256                         ostream << *i;
257                 }
258         }
259         char c = *( i - 1 );
260         if ( c != '/' && c != '\\' ) {
261                 ostream << '/';
262         }
263         return ostream;
264 }
265
266
267 #endif