1 /************************************************************************** 2 * 3 * Copyright 2011 Jose Fonseca 4 * All Rights Reserved. 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to deal 8 * in the Software without restriction, including without limitation the rights 9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 * copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in 14 * all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 * THE SOFTWARE. 23 * 24 **************************************************************************/ 25 26 /* 27 * String manipulation. 28 */ 29 30 #pragma once 31 32 33 #include <assert.h> 34 #include <stdarg.h> 35 #include <stdio.h> 36 #include <stddef.h> 37 38 #ifdef __MINGW32__ 39 // Some versions of MinGW are missing _vscprintf's declaration, although they 40 // still provide the symbol in the import library. 41 extern "C" _CRTIMP int _vscprintf(const char *format, va_list argptr); 42 #endif 43 44 #ifndef va_copy 45 #ifdef __va_copy 46 #define va_copy(dest, src) __va_copy((dest), (src)) 47 #else 48 #define va_copy(dest, src) (dest) = (src) 49 #endif 50 #endif 51 52 #include <vector> 53 54 #include "os.hpp" 55 56 57 #ifdef _WIN32 58 #define OS_DIR_SEP '\\' 59 #define OS_PATH_SEP ';' 60 #else /* !_WIN32 */ 61 #define OS_DIR_SEP '/' 62 #define OS_PATH_SEP ':' 63 #endif /* !_WIN32 */ 64 65 66 namespace os { 67 68 69 /** 70 * Class to represent zero-terminated strings, based upon std::vector<char>, 71 * suitable for passing strings or paths to/from OS calls. 72 * 73 * Both Win32 and POSIX APIs return strings as zero length buffers. Although 74 * std::string provides an easy method to obtain a read-only pointer to a zero 75 * terminated string, it lacks the ability to return a read-write pointer. So 76 * there is no way to tell OS calls to write into a std::string directly -- a 77 * temporary malloc'ed string would be necessary --, which would be 78 * unnecessarily inefficient, specially considering that these strings would 79 * ultimately passed back to the OS, which would again expect zero-terminated 80 * strings. 81 * 82 * This class is not, however, a full replacement for std::string, which should 83 * be otherwise used whenever possible. 84 */ 85 class String { 86 protected: 87 typedef std::vector<char> Buffer; 88 89 /** 90 * The buffer's last element is always the '\0' character, therefore the 91 * buffer must never be empty. 92 */ 93 Buffer buffer; 94 find(char c)95 Buffer::iterator find(char c) { 96 Buffer::iterator it = buffer.begin(); 97 assert(it != buffer.end()); 98 while (it != buffer.end()) { 99 if (*it == c) { 100 return it; 101 } 102 ++it; 103 } 104 return buffer.end(); 105 } 106 rfind(char c)107 Buffer::iterator rfind(char c) { 108 Buffer::iterator it = buffer.end(); 109 110 // Skip trailing '\0' 111 assert(it != buffer.begin()); 112 --it; 113 assert(*it == '\0'); 114 115 while (it != buffer.begin()) { 116 --it; 117 if (*it == c) { 118 return it; 119 } 120 } 121 122 return buffer.end(); 123 } 124 String(size_t size)125 String(size_t size) : 126 buffer(size) { 127 } 128 buf(void)129 char *buf(void) { 130 return &buffer[0]; 131 } 132 133 inline bool isSep(char c)134 isSep(char c) { 135 if (c == '/') { 136 return true; 137 } 138 #ifdef _WIN32 139 if (c == '\\') { 140 return true; 141 } 142 #endif 143 return false; 144 } 145 146 public: 147 rfindSep(bool skipTrailing=true)148 Buffer::iterator rfindSep(bool skipTrailing = true) { 149 Buffer::iterator it = end(); 150 151 // Skip trailing separators 152 if (skipTrailing) { 153 while (it != buffer.begin()) { 154 --it; 155 if (isSep(*it)) { 156 // Halt if find the root 157 if (it == buffer.begin()) { 158 return it; 159 } 160 } else { 161 break; 162 } 163 } 164 } 165 166 // Advance to the last separator 167 while (it != buffer.begin()) { 168 --it; 169 if (isSep(*it)) { 170 return it; 171 } 172 } 173 174 return end(); 175 } 176 177 /* 178 * Constructors 179 */ 180 String(void)181 String(void) { 182 buffer.push_back(0); 183 } 184 String(const char * s)185 String(const char *s) : 186 buffer(s, s + strlen(s) + 1) 187 {} 188 String(const String & other)189 String(const String &other) : 190 buffer(other.buffer) 191 {} 192 193 template <class InputIterator> String(InputIterator first,InputIterator last)194 String(InputIterator first, InputIterator last) : 195 buffer(first, last) 196 { 197 buffer.push_back(0); 198 } 199 200 /** 201 * From a printf-like format string 202 */ 203 static String format(const char * format,...)204 format(const char *format, ...) 205 #if defined(__MINGW32__) 206 __attribute__ ((format (__MINGW_PRINTF_FORMAT, 1, 2))) 207 #elif defined(__GNUC__) 208 __attribute__ ((format (printf, 1, 2))) 209 #endif 210 { 211 212 va_list args; 213 214 va_start(args, format); 215 216 int length; 217 va_list args_copy; 218 va_copy(args_copy, args); 219 #ifdef _WIN32 220 /* We need to use _vscprintf to calculate the length as vsnprintf returns -1 221 * if the number of characters to write is greater than count. 222 */ 223 length = _vscprintf(format, args_copy); 224 #else 225 char dummy; 226 length = vsnprintf(&dummy, sizeof dummy, format, args_copy); 227 #endif 228 va_end(args_copy); 229 230 assert(length >= 0); 231 size_t size = length + 1; 232 233 String path(size); 234 235 va_start(args, format); 236 vsnprintf(path.buf(), size, format, args); 237 va_end(args); 238 239 return path; 240 } 241 242 /* 243 * Conversion to ordinary C strings. 244 */ 245 str(void) const246 const char *str(void) const { 247 assert(buffer.back() == 0); 248 return &buffer[0]; 249 } 250 operator const char*(void) const251 operator const char *(void) const { 252 return str(); 253 } 254 255 /* 256 * Iterators 257 */ 258 259 typedef Buffer::const_iterator const_iterator; 260 typedef Buffer::iterator iterator; 261 begin(void) const262 const_iterator begin(void) const { 263 return buffer.begin(); 264 } 265 begin(void)266 iterator begin(void) { 267 return buffer.begin(); 268 } 269 end(void) const270 const_iterator end(void) const { 271 const_iterator it = buffer.end(); 272 assert(it != buffer.begin()); 273 --it; // skip null 274 return it; 275 } 276 end(void)277 iterator end(void) { 278 iterator it = buffer.end(); 279 assert(it != buffer.begin()); 280 --it; // skip null 281 return it; 282 } 283 284 /* 285 * Operations 286 */ 287 insert(iterator position,char c)288 void insert(iterator position, char c) { 289 buffer.insert(position, c); 290 } 291 292 template <class InputIterator> insert(iterator position,InputIterator first,InputIterator last)293 void insert(iterator position, InputIterator first, InputIterator last) { 294 buffer.insert(position, first, last); 295 } 296 insert(iterator position,const char * s)297 void insert(iterator position, const char *s) { 298 assert(s); 299 insert(position, s, s + strlen(s)); 300 } 301 insert(iterator position,const String & other)302 void insert(iterator position, const String & other) { 303 insert(position, other.begin(), other.end()); 304 } 305 append(char c)306 void append(char c) { 307 insert(end(), c); 308 } 309 310 template <class InputIterator> append(InputIterator first,InputIterator last)311 void append(InputIterator first, InputIterator last) { 312 insert(end(), first, last); 313 } 314 append(const char * s)315 void append(const char *s) { 316 insert(end(), s); 317 } 318 append(const String & other)319 void append(const String & other) { 320 insert(end(), other); 321 } 322 323 template <class InputIterator> erase(InputIterator first,InputIterator last)324 void erase(InputIterator first, InputIterator last) { 325 buffer.erase(first, last); 326 } 327 328 /** 329 * Get a writable buffer with the specified size. 330 * 331 * truncate() must be called after the buffer is written, and before any other 332 * method is called. 333 * 334 * Between the call to buf() and truncate() methods, the `buffer.back() == 335 * 0` invariant will not hold true. 336 */ buf(size_t size)337 char *buf(size_t size) { 338 buffer.resize(size); 339 return &buffer[0]; 340 } 341 length(void) const342 size_t length(void) const { 343 size_t size = buffer.size(); 344 assert(size > 0); 345 assert(buffer[size - 1] == 0); 346 return size - 1; 347 } 348 349 /** 350 * Truncate the string to the specified length. 351 */ truncate(size_t length)352 void truncate(size_t length) { 353 assert(length < buffer.size()); 354 buffer[length] = 0; 355 assert(strlen(&buffer[0]) == length); 356 buffer.resize(length + 1); 357 } 358 359 /** 360 * Truncate the string to the first zero character. 361 */ truncate(void)362 void truncate(void) { 363 truncate(strlen(&buffer[0])); 364 } 365 366 367 /* 368 * Path manipulation 369 */ 370 371 bool 372 exists(void) const; 373 374 /* Trim directory (leaving base filename). 375 */ trimDirectory(void)376 void trimDirectory(void) { 377 iterator sep = rfindSep(); 378 if (sep != end()) { 379 buffer.erase(buffer.begin(), sep + 1); 380 } 381 } 382 383 /* Trim filename component (leaving containing directory). 384 * 385 * - trailing separators are ignored 386 * - a path with no separator at all yields "." 387 * - a path consisting of just the root directory is left unchanged 388 */ trimFilename(void)389 void trimFilename(void) { 390 iterator sep = rfindSep(); 391 392 // No separator found, so return '.' 393 if (sep == end()) { 394 buffer.resize(2); 395 buffer[0] = '.'; 396 buffer[1] = 0; 397 return; 398 } 399 400 // Root. Nothing to do. 401 if (sep == buffer.begin()) { 402 return; 403 } 404 405 // Trim filename 406 buffer.erase(sep, end()); 407 } 408 trimExtension(void)409 void trimExtension(void) { 410 iterator dot = rfind('.'); 411 if (dot != buffer.end()) { 412 buffer.erase(dot, end()); 413 } 414 } 415 join(const String & other)416 void join(const String & other) { 417 if (length() && end()[-1] != OS_DIR_SEP) { 418 append(OS_DIR_SEP); 419 } 420 append(other.begin(), other.end()); 421 } 422 }; 423 424 425 String getProcessName(); 426 String getCurrentDir(); 427 428 String getConfigDir(); 429 430 bool createDirectory(const String &path); 431 432 bool copyFile(const String &srcFileName, const String &dstFileName, bool override = true); 433 434 bool removeFile(const String &fileName); 435 436 String getTemporaryDirectoryPath(void); 437 438 } /* namespace os */ 439 440