ok
[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 /// \file
26 /// \brief OS file-system path comparison, decomposition and manipulation.
27 ///
28 /// - Paths are c-style null-terminated-character-arrays.
29 /// - Path separators must be forward slashes (unix style).
30 /// - Directory paths must end in a separator.
31 /// - Paths must not contain the ascii characters \\ : * ? " < > or |.
32 /// - Paths may be encoded in UTF-8 or any extended-ascii character set.
33
34 #include "string/string.h"
35
36 #if defined(WIN32)
37 #define OS_CASE_INSENSITIVE
38 #endif
39
40 /// \brief Returns true if \p path is lexicographically sorted before \p other.
41 /// If both \p path and \p other refer to the same file, neither will be sorted before the other.
42 /// O(n)
43 inline bool path_less(const char* path, const char* other)
44 {
45 #if defined(OS_CASE_INSENSITIVE)
46   return string_less_nocase(path, other);
47 #else
48   return string_less(path, other);
49 #endif
50 }
51
52 /// \brief Returns <0 if \p path is lexicographically less than \p other.
53 /// Returns >0 if \p path is lexicographically greater than \p other.
54 /// Returns 0 if both \p path and \p other refer to the same file.
55 /// O(n)
56 inline int path_compare(const char* path, const char* other)
57 {
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 {
69 #if defined(OS_CASE_INSENSITIVE)
70   return string_equal_nocase(path, other);
71 #else
72   return string_equal(path, other);
73 #endif
74 }
75
76 /// \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.
77 /// If the paths are UTF-8 encoded, [\p path, \p path + \p n) must be a complete path.
78 /// O(n)
79 inline bool path_equal_n(const char* path, const char* other, std::size_t n)
80 {
81 #if defined(OS_CASE_INSENSITIVE)
82   return string_equal_nocase_n(path, other, n);
83 #else
84   return string_equal_n(path, other, n);
85 #endif
86 }
87
88
89 /// \brief Returns true if \p path is a fully qualified file-system path.
90 /// O(1)
91 inline bool path_is_absolute(const char* path)
92 {
93 #if defined(WIN32)
94   return path[0] == '/'
95     || (path[0] != '\0' && path[1] == ':'); // local drive
96 #elif defined(__linux__) || defined(__APPLE__)
97   return path[0] == '/';
98 #endif
99 }
100
101 /// \brief Returns true if \p path is a directory.
102 /// O(n)
103 inline bool path_is_directory(const char* path)
104 {
105   std::size_t length = strlen(path);
106   if(length > 0)
107   {
108     return path[length-1] == '/';
109   }
110   return false;
111 }
112
113 /// \brief Returns a pointer to the first character of the component of \p path following the first directory component.
114 /// O(n)
115 inline const char* path_remove_directory(const char* path)
116 {
117   const char* first_separator = strchr(path, '/');
118   if(first_separator != 0)
119   {
120     return ++first_separator;
121   }
122   return "";
123 }
124
125 /// \brief Returns a pointer to the first character of the filename component of \p path.
126 /// O(n)
127 inline const char* path_get_filename_start(const char* path)
128 {
129   {
130     const char* last_forward_slash = strrchr(path, '/');
131     if(last_forward_slash != 0)
132     {
133       return last_forward_slash + 1;
134     }
135   }
136
137   // not strictly necessary,since paths should not contain '\'
138   {
139     const char* last_backward_slash = strrchr(path, '\\');
140     if(last_backward_slash != 0)
141     {
142       return last_backward_slash + 1;
143     }
144   }
145
146   return path;
147 }
148
149 /// \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.
150 /// O(n)
151 inline const char* path_get_filename_base_end(const char* path)
152 {
153   const char* last_period = strrchr(path_get_filename_start(path), '.');
154   return (last_period != 0) ? last_period : path + string_length(path);
155 }
156
157 /// \brief Returns the length of the filename component (not including extension) of \p path.
158 /// O(n)
159 inline std::size_t path_get_filename_base_length(const char* path)
160 {
161   return path_get_filename_base_end(path) - path;
162 }
163
164 /// \brief If \p path is a child of \p base, returns the subpath relative to \p base, else returns \p path.
165 /// O(n)
166 inline const char* path_make_relative(const char* path, const char* base)
167 {
168   const std::size_t length = string_length(base);
169   if(path_equal_n(path, base, length))
170   {
171     return path + length;
172   }
173   return path;
174 }
175
176 /// \brief Returns a pointer to the first character of the file extension of \p path, or "" if not found.
177 /// O(n)
178 inline const char* path_get_extension(const char* path)
179 {
180   const char* last_period = strrchr(path_get_filename_start(path), '.');
181   if(last_period != 0)
182   {
183     return ++last_period;
184   }
185   return "";
186 }
187
188 /// \brief Returns true if \p extension is of the same type as \p other.
189 /// O(n)
190 inline bool extension_equal(const char* extension, const char* other)
191 {
192   return path_equal(extension, other);
193 }
194
195 template<typename Functor>
196 class MatchFileExtension
197 {
198   const char* m_extension;
199   const Functor& m_functor;
200 public:
201   MatchFileExtension(const char* extension, const Functor& functor) : m_extension(extension), m_functor(functor)
202   {
203   }
204   void operator()(const char* name) const
205   {
206     const char* extension = path_get_extension(name);
207     if(extension_equal(extension, m_extension))
208     {
209       m_functor(name);
210     }
211   }
212 };
213
214 /// \brief A functor which invokes its contained \p functor if the \p name argument matches its \p extension.
215 template<typename Functor>
216 inline MatchFileExtension<Functor> matchFileExtension(const char* extension, const Functor& functor)
217 {
218   return MatchFileExtension<Functor>(extension, functor);
219 }
220
221 class PathCleaned
222 {
223 public:
224   const char* m_path;
225   PathCleaned(const char* path) : m_path(path)
226   {
227   }
228 };
229
230 /// \brief Writes \p path to \p ostream with dos-style separators replaced by unix-style separators.
231 template<typename TextOutputStreamType>
232 TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const PathCleaned& path)
233 {
234   const char* i = path.m_path;
235   for(; *i != '\0'; ++i)
236   {
237     if(*i == '\\')
238     {
239       ostream << '/';
240     }
241     else
242     {
243       ostream << *i;
244     }
245   }
246   return ostream;
247 }
248
249 class DirectoryCleaned
250 {
251 public:
252   const char* m_path;
253   DirectoryCleaned(const char* path) : m_path(path)
254   {
255   }
256 };
257
258 /// \brief Writes \p path to \p ostream with dos-style separators replaced by unix-style separators, and appends a separator if necessary.
259 template<typename TextOutputStreamType>
260 TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const DirectoryCleaned& path)
261 {
262   const char* i = path.m_path;
263   for(; *i != '\0'; ++i)
264   {
265     if(*i == '\\')
266     {
267       ostream << '/';
268     }
269     else
270     {
271       ostream << *i;
272     }
273   }
274   char c = *(i - 1);
275   if(c != '/' && c != '\\')
276   {
277     ostream << '/';
278   }
279   return ostream;
280 }
281
282
283 #endif