You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4756 lines
126 KiB
C++

// Public Domain. See "unlicense" statement at the end of this file.
// USAGE
//
// This is a single-file library. To use it, do something like the following in one .c file.
// #define DR_IMPLEMENTATION
// #include "dr.h"
//
// You can then #include dr.h in other parts of the program as you would with any other header file.
//
//
//
// OPTIONS
//
// #define DR_UTIL_WIN32_USE_CRITICAL_SECTION_MUTEX
// - Use the Win32 CRITICAL_SECTION API for mutex objects.
#ifndef dr_util_h
#define dr_util_h
#ifdef __cplusplus
extern "C" {
#endif
// Disable MSVC compatibility if we're compiling with it.
#if defined(_MSC_VER) || defined(__MINGW32__)
#define DR_NO_MSVC_COMPAT
#endif
#if defined(_MSC_VER)
#define DR_INLINE static __inline
#else
#define DR_INLINE static inline
#endif
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdio.h>
#ifndef DR_NO_MSVC_COMPAT
#include <errno.h>
#endif
#ifndef DR_SIZED_TYPES_DEFINED
#define DR_SIZED_TYPES_DEFINED
#if defined(_MSC_VER) && _MSC_VER < 1600
typedef signed char dr_int8;
typedef unsigned char dr_uint8;
typedef signed short dr_int16;
typedef unsigned short dr_uint16;
typedef signed int dr_int32;
typedef unsigned int dr_uint32;
typedef signed __int64 dr_int64;
typedef unsigned __int64 dr_uint64;
#else
#include <stdint.h>
typedef int8_t dr_int8;
typedef uint8_t dr_uint8;
typedef int16_t dr_int16;
typedef uint16_t dr_uint16;
typedef int32_t dr_int32;
typedef uint32_t dr_uint32;
typedef int64_t dr_int64;
typedef uint64_t dr_uint64;
#endif
typedef dr_uint8 dr_bool8;
typedef dr_uint32 dr_bool32;
#define DR_TRUE 1
#define DR_FALSE 0
#endif
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
/////////////////////////////////////////////////////////
// Annotations
#ifndef IN
#define IN
#endif
#ifndef OUT
#define OUT
#endif
#ifndef UNUSED
#define UNUSED(x) ((void)(x))
#endif
/////////////////////////////////////////////////////////
// min/max/clamp
#ifndef dr_min
#define dr_min(x, y) (((x) < (y)) ? (x) : (y))
#endif
#ifndef dr_max
#define dr_max(x, y) (((x) > (y)) ? (x) : (y))
#endif
#ifndef dr_clamp
#define dr_clamp(x, low, high) (dr_max(low, dr_min(x, high)))
#endif
#ifndef dr_round_up
#define dr_round_up(x, multiple) ((((x) + ((multiple) - 1)) / (multiple)) * (multiple))
#endif
#ifndef dr_round_up_signed
#define dr_round_up_signed(x, multiple) ((((x) + (((x) >= 0)*((multiple) - 1))) / (multiple)) * (multiple))
#endif
DR_INLINE dr_uint32 dr_next_power_of_2(dr_uint32 value)
{
--value;
value = (value >> 1) | value;
value = (value >> 2) | value;
value = (value >> 4) | value;
value = (value >> 8) | value;
value = (value >> 16) | value;
return value + 1;
}
#define dr_abs(x) (((x) < 0) ? (-(x)) : (x))
/////////////////////////////////////////////////////////
// MSVC Compatibility
// A basic implementation of MSVC's strcpy_s().
int dr_strcpy_s(char* dst, size_t dstSizeInBytes, const char* src);
// A basic implementation of MSVC's strncpy_s().
int dr_strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count);
// A basic implementation of MSVC's strcat_s().
int dr_strcat_s(char* dst, size_t dstSizeInBytes, const char* src);
// A basic implementation of MSVC's strncat_s()
int dr_strncat_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count);
// A basic implementation of MSVC's _atoi_s()
int dr_itoa_s(int value, char* dst, size_t dstSizeInBytes, int radix);
#ifndef DR_NO_MSVC_COMPAT
#ifndef _TRUNCATE
#define _TRUNCATE ((size_t)-1)
#endif
DR_INLINE int strcpy_s(char* dst, size_t dstSizeInBytes, const char* src)
{
return dr_strcpy_s(dst, dstSizeInBytes, src);
}
DR_INLINE int strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count)
{
return dr_strncpy_s(dst, dstSizeInBytes, src, count);
}
DR_INLINE int strcat_s(char* dst, size_t dstSizeInBytes, const char* src)
{
return dr_strcat_s(dst, dstSizeInBytes, src);
}
DR_INLINE int strncat_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count)
{
return dr_strncat_s(dst, dstSizeInBytes, src, count);
}
#ifndef __MINGW32__
DR_INLINE int _stricmp(const char* string1, const char* string2)
{
return strcasecmp(string1, string2);
}
#endif
DR_INLINE int _itoa_s(int value, char* dst, size_t dstSizeInBytes, int radix)
{
return dr_itoa_s(value, dst, dstSizeInBytes, radix);
}
#endif
/////////////////////////////////////////////////////////
// String Helpers
// Determines if the given character is whitespace.
dr_bool32 dr_is_whitespace(dr_uint32 utf32);
/// Removes every occurance of the given character from the given string.
void dr_strrmchar(char* str, char c);
/// Finds the first non-whitespace character in the given string.
const char* dr_first_non_whitespace(const char* str);
static inline const char* dr_ltrim(const char* str) { return dr_first_non_whitespace(str); }
static const char* dr_rtrim(const char* str);
/// Trims both the leading and trailing whitespace from the given string.
void dr_trim(char* str);
/// Finds the first occurance of a whitespace character in the given string.
const char* dr_first_whitespace(const char* str);
/// Finds the beginning of the next line.
const char* dr_next_line(const char* str);
/// Makes a copy of the first line of the given string.
size_t dr_copy_line(const char* str, char* lineOut, size_t lineOutSize);
// A slow string replace function. Free the returned string with free().
char* dr_string_replace(const char* src, const char* query, const char* replacement);
// Replaces an ASCII character with another in the given string.
void dr_string_replace_ascii(char* src, char c, char replacement);
/////////////////////////////////////////////////////////
// Unicode Utilities
/// Converts a UTF-32 character to UTF-16.
///
/// @param utf16 [in] A pointer to an array of at least two 16-bit values that will receive the UTF-16 character.
///
/// @return 2 if the returned character is a surrogate pair, 1 if it's a simple UTF-16 code point, or 0 if it's an invalid character.
///
/// @remarks
/// It is assumed the <utf16> is large enough to hold at least 2 unsigned shorts. <utf16> will be padded with 0 for unused
/// components.
DR_INLINE int dr_utf32_to_utf16_ch(unsigned int utf32, unsigned short utf16[2])
{
if (utf16 == NULL) {
return 0;
}
if (utf32 < 0xD800 || (utf32 >= 0xE000 && utf32 <= 0xFFFF))
{
utf16[0] = (unsigned short)utf32;
utf16[1] = 0;
return 1;
}
else
{
if (utf32 >= 0x10000 && utf32 <= 0x10FFFF)
{
utf16[0] = (unsigned short)(0xD7C0 + (unsigned short)(utf32 >> 10));
utf16[1] = (unsigned short)(0xDC00 + (unsigned short)(utf32 & 0x3FF));
return 2;
}
else
{
// Invalid.
utf16[0] = 0;
utf16[1] = 0;
return 0;
}
}
}
/// Converts a UTF-16 character to UTF-32.
DR_INLINE unsigned int dr_utf16_to_utf32_ch(unsigned short utf16[2])
{
if (utf16 == NULL) {
return 0;
}
if (utf16[0] < 0xD800 || utf16[0] > 0xDFFF)
{
return utf16[0];
}
else
{
if ((utf16[0] & 0xFC00) == 0xD800 && (utf16[1] & 0xFC00) == 0xDC00)
{
return ((unsigned int)utf16[0] << 10) + utf16[1] - 0x35FDC00;
}
else
{
// Invalid.
return 0;
}
}
}
/// Converts a UTF-16 surrogate pair to UTF-32.
DR_INLINE unsigned int dr_utf16pair_to_utf32_ch(unsigned short utf160, unsigned short utf161)
{
unsigned short utf16[2];
utf16[0] = utf160;
utf16[1] = utf161;
return dr_utf16_to_utf32_ch(utf16);
}
/////////////////////////////////////////////////////////
// Aligned Allocations
#ifndef DRUTIL_NO_ALIGNED_MALLOC
DR_INLINE void* dr_aligned_malloc(size_t alignment, size_t size)
{
#if defined(_WIN32) || defined(_WIN64)
return _aligned_malloc(size, alignment);
#else
void* pResult;
if (posix_memalign(&pResult, alignment, size) == 0) {
return pResult;
}
return 0;
#endif
}
DR_INLINE void dr_aligned_free(void* ptr)
{
#if defined(_WIN32) || defined(_WIN64)
_aligned_free(ptr);
#else
free(ptr);
#endif
}
#endif // !DRUTIL_NO_ALIGNED_MALLOC
/////////////////////////////////////////////////////////
// Key/Value Pair Parsing
typedef size_t (* dr_key_value_read_proc) (void* pUserData, void* pDataOut, size_t bytesToRead);
typedef void (* dr_key_value_pair_proc) (void* pUserData, const char* key, const char* value);
typedef void (* dr_key_value_error_proc)(void* pUserData, const char* message, unsigned int line);
/// Parses a series of simple Key/Value pairs.
///
/// @remarks
/// This function is suitable for parsing simple key/value config files.
/// @par
/// This function will never allocate memory on the heap. Because of this there is a minor restriction in the length of an individual
/// key/value pair which is 4KB.
/// @par
/// Formatting rules are as follows:
/// - The basic syntax for a key/value pair is [key][whitespace][value]. Example: MyProperty 1234
/// - All key/value pairs must be declared on a single line, and a single line cannot contain more than a single key/value pair.
/// - Comments begin with the '#' character and continue until the end of the line.
/// - A key cannot contain spaces but are permitted in values.
/// - The value will have any leading and trailing whitespace trimmed.
/// @par
/// If an error occurs, that line will be skipped and processing will continue.
void dr_parse_key_value_pairs(dr_key_value_read_proc onRead, dr_key_value_pair_proc onPair, dr_key_value_error_proc onError, void* pUserData);
// This will only return DR_FALSE if the file fails to open. It will still return DR_TRUE even if there are syntax error or whatnot.
dr_bool32 dr_parse_key_value_pairs_from_file(const char* filePath, dr_key_value_pair_proc onPair, dr_key_value_error_proc onError, void* pUserData);
/////////////////////////////////////////////////////////
// Basic Tokenizer
/// Retrieves the first token in the given string.
///
/// @remarks
/// This function is suitable for doing a simple whitespace tokenization of a null-terminated string.
/// @par
/// The return value is a pointer to one character past the last character of the next token. You can use the return value to execute
/// this function in a loop to parse an entire string.
/// @par
/// <tokenOut> can be null. If the buffer is too small to contain the entire token it will be set to an empty string. The original
/// input string combined with the return value can be used to reliably find the token.
/// @par
/// This will handle double-quoted strings, so a string such as "My \"Complex String\"" contains two tokens: "My" and "\"Complex String\"".
/// @par
/// This function has no dependencies.
const char* dr_next_token(const char* tokens, char* tokenOut, size_t tokenOutSize);
/////////////////////////////////////////////////////////
// Known Folders
/// Retrieves the path of the executable.
///
/// @remarks
/// Currently only works on Windows and Linux. Other platforms will be added as they're needed.
dr_bool32 dr_get_executable_path(char* pathOut, size_t pathOutSize);
/// Retrieves the path of the directory containing the executable.
///
/// @remarks
/// Currently only works on Windows and Linux. Other platforms will be added as they're needed.
dr_bool32 dr_get_executable_directory_path(char* pathOut, size_t pathOutSize);
/// Retrieves the path of the user's config directory.
///
/// @remarks
/// On Windows this will typically be %APPDATA% and on Linux it will usually be ~/.config
dr_bool32 dr_get_config_folder_path(char* pathOut, size_t pathOutSize);
/// Retrieves the path of the user's log directory.
///
/// @remarks
/// On Windows this will typically be %APPDATA% and on Linux it will usually be var/log
dr_bool32 dr_get_log_folder_path(char* pathOut, size_t pathOutSize);
/// Retrieves the current directory.
const char* dr_get_current_directory(char* pathOut, size_t pathOutSize);
/// Sets the current directory.
dr_bool32 dr_set_current_directory(const char* path);
/////////////////////////////////////////////////////////
// Basic File Management
// Callback function for file iteration.
typedef dr_bool32 (* dr_iterate_files_proc)(const char* filePath, void* pUserData);
// Helper for opening a stdio FILE.
FILE* dr_fopen(const char* fileName, const char* openMode);
// Helper for creating an empty file.
dr_bool32 dr_create_empty_file(const char* fileName, dr_bool32 failIfExists);
// Retrieves the file data of the given file. Free the returned pointer with dr_free_file_data().
void* dr_open_and_read_file(const char* filePath, size_t* pFileSizeOut);
// Retrieves the file data of the given file as a null terminated string. Free the returned pointer with dr_free_file_data(). The
// returned file size is the length of the string not including the null terminator.
char* dr_open_and_read_text_file(const char* filePath, size_t* pFileSizeOut);
// Creates a new file with the given data.
dr_bool32 dr_open_and_write_file(const char* filePath, const void* pData, size_t dataSize);
// Creates a new file with the given string.
dr_bool32 dr_open_and_write_text_file(const char* filePath, const char* text);
// Frees the file data returned by dr_open_and_read_file().
void dr_free_file_data(void* valueReturnedByOpenAndReadFile);
// Determines whether or not the given file path is to a file.
//
// This will return DR_FALSE if the path points to a directory.
dr_bool32 dr_file_exists(const char* filePath);
// Determines whether or not the given file path points to a directory.
//
// This will return DR_FALSE if the path points to a file.
dr_bool32 dr_directory_exists(const char* directoryPath);
static inline dr_bool32 dr_is_directory(const char* directoryPath) { return dr_directory_exists(directoryPath); }
// Moves a file.
//
// This uses rename() on POSIX platforms and MoveFileEx(oldPath, newPath, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH) on windows platforms.
dr_bool32 dr_move_file(const char* oldPath, const char* newPath);
// Copies a file.
dr_bool32 dr_copy_file(const char* srcPath, const char* dstPath, dr_bool32 failIfExists);
// Determines if the given file is read only.
dr_bool32 dr_is_file_read_only(const char* filePath);
// Retrieves the last modified time of the file at the given path.
dr_uint64 dr_get_file_modified_time(const char* filePath);
// Deletes the file at the given path.
//
// This uses remove() on POSIX platforms and DeleteFile() on Windows platforms.
dr_bool32 dr_delete_file(const char* filePath);
// Cross-platform wrapper for creating a directory.
dr_bool32 dr_mkdir(const char* directoryPath);
// Recursively creates a directory.
dr_bool32 dr_mkdir_recursive(const char* directoryPath);
// Iterates over every file and folder of the given directory.
dr_bool32 dr_iterate_files(const char* directory, dr_bool32 recursive, dr_iterate_files_proc proc, void* pUserData);
/////////////////////////////////////////////////////////
// DPI Awareness
#if defined(_WIN32)
/// Win32 Only: Makes the application DPI aware.
void dr_win32_make_dpi_aware();
/// Win32 Only: Retrieves the base DPI to use as a reference when calculating DPI scaling.
void dr_win32_get_base_dpi(int* pDPIXOut, int* pDPIYOut);
/// Win32 Only: Retrieves the system-wide DPI.
void dr_win32_get_system_dpi(int* pDPIXOut, int* pDPIYOut);
/// Win32 Only: Retrieves the actual DPI of the monitor at the given index.
///
/// @remarks
/// If per-monitor DPI is not supported, the system wide DPI settings will be used instead.
/// @par
/// This runs in linear time.
void dr_win32_get_monitor_dpi(int monitor, int* pDPIXOut, int* pDPIYOut);
/// Win32 Only: Retrieves the number of monitors active at the time of calling.
///
/// @remarks
/// This runs in linear time.
int dr_win32_get_monitor_count();
#endif
/////////////////////////////////////////////////////////
// Date / Time
/// Retrieves a time_t as of the time the function was called.
time_t dr_now();
/// Formats a data/time string.
void dr_datetime_short(time_t t, char* strOut, unsigned int strOutSize);
// Returns a date string in YYYYMMDD format.
void dr_date_YYYYMMDD(time_t t, char* strOut, unsigned int strOutSize);
/////////////////////////////////////////////////////////
// Command Line
//
// The command line functions below are just simple iteration functions. This command line system is good for
// simple command lines, but probably not the best for programs requiring complex command line work.
//
// For argv style command lines, parse_cmdline() will run without any heap allocations. With a Win32 style
// command line there will be one malloc() per call fo parse_cmdline(). This is the only function that will do
// a malloc().
//
// Below is an example:
//
// dr_cmdline cmdline;
// if (dr_init_cmdline(&cmdline, argc, argv)) {
// dr_parse_cmdline(&cmdline, my_cmdline_handler, pMyUserData);
// }
//
// void my_cmdline_handler(const char* key, const char* value, void* pUserData)
// {
// // Do something...
// }
//
//
// When parsing the command line, the first iteration will be the program path and the key will be "[path]".
//
// For segments such as "-abcd", the callback will be called for "a", "b", "c", "d" individually, with the
// value set to NULL.
//
// For segments such as "--server", the callback will be called for "server", with the value set to NULL.
//
// For segments such as "-f file.txt", the callback will be called with the key set to "f" and the value set
// to "file.txt".
//
// For segments such as "-f file1.txt file2.txt", the callback will be called twice, once for file1.txt and
// again for file2.txt, with with the key set to "f" in both cases.
//
// For segments where there is no leading key, the values will be posted as annonymous (key set to NULL). An example
// is "my_program.exe file1.txt file2.txt", in which case the first iteration will be the program path, the second iteration
// will be "file1.txt", with the key set to NULL. The third iteration will be "file2.txt" with the key set to NULL.
//
// For segments such as "-abcd file.txt", "a", "b", "c", "d" will be sent with NULL values, and "file.txt" will be
// posted with a NULL key.
typedef struct dr_cmdline dr_cmdline;
struct dr_cmdline
{
// argv style.
int argc;
char** argv;
// Win32 style
const char* win32;
};
typedef dr_bool32 dr_cmdline_parse_proc(const char* key, const char* value, void* pUserData);
/// Initializes a command line object.
dr_bool32 dr_init_cmdline(dr_cmdline* pCmdLine, int argc, char** argv);
/// Initializes a command line object using a Win32 style command line.
dr_bool32 dr_init_cmdline_win32(dr_cmdline* pCmdLine, const char* args);
/// Parses the given command line.
void dr_parse_cmdline(dr_cmdline* pCmdLine, dr_cmdline_parse_proc callback, void* pUserData);
/// Helper for determining whether or not the given key exists.
dr_bool32 dr_cmdline_key_exists(dr_cmdline* pCmdLine, const char* key);
// Convers the given command line object to argc/argv style.
//
// Returns the argument count. Returns 0 if an error occurs. Free "argvOut" with dr_free_argv().
int dr_cmdline_to_argv(dr_cmdline* pCmdLine, char*** argvOut);
// Converts a WinMain style command line to argc/argv.
//
// Returns the argument count. Returns 0 if an error occurs. Free "argvOut" with dr_free_argv().
int dr_winmain_to_argv(const char* cmdlineWinMain, char*** argvOut);
// Frees the argc/argv command line that was generated by dr.h
void dr_free_argv(char** argv);
/////////////////////////////////////////////////////////
// Threading
/// Puts the calling thread to sleep for approximately the given number of milliseconds.
///
/// @remarks
/// This is not 100% accurate and should be considered an approximation.
void dr_sleep(unsigned int milliseconds);
void dr_yield();
/// Retrieves the number of logical cores on system.
unsigned int dr_get_logical_processor_count();
/// Thread.
typedef void* dr_thread;
typedef int (* dr_thread_entry_proc)(void* pData);
/// Creates and begins executing a new thread.
///
/// @remarks
/// This will not return until the thread has entered into it's entry point.
/// @par
/// Creating a thread should be considered an expensive operation. For high performance, you should create threads
/// at load time and cache them.
dr_thread dr_create_thread(dr_thread_entry_proc entryProc, void* pData);
/// Deletes the given thread.
///
/// @remarks
/// This does not actually exit the thread, but rather deletes the memory that was allocated for the thread
/// object returned by dr_create_thread().
/// @par
/// It is usually best to wait for the thread to terminate naturally with dr_wait_thread() before calling
/// this function, however it is still safe to do something like the following.
/// @code
/// dr_delete_thread(dr_create_thread(my_thread_proc, pData))
/// @endcode
void dr_delete_thread(dr_thread thread);
/// Waits for the given thread to terminate.
void dr_wait_thread(dr_thread thread);
/// Helper function for waiting for a thread and then deleting the handle after it has terminated.
void dr_wait_and_delete_thread(dr_thread thread);
/// Mutex
typedef void* dr_mutex;
/// Creates a mutex object.
///
/// @remarks
/// If an error occurs, 0 is returned. Otherwise a handle the size of a pointer is returned.
dr_mutex dr_create_mutex();
/// Deletes a mutex object.
void dr_delete_mutex(dr_mutex mutex);
/// Locks the given mutex.
void dr_lock_mutex(dr_mutex mutex);
/// Unlocks the given mutex.
void dr_unlock_mutex(dr_mutex mutex);
/// Semaphore
typedef void* dr_semaphore;
/// Creates a semaphore object.
///
/// @remarks
/// If an error occurs, 0 is returned. Otherwise a handle the size of a pointer is returned.
dr_semaphore dr_create_semaphore(int initialValue);
/// Deletes the given semaphore.
void dr_delete_semaphore(dr_semaphore semaphore);
/// Waits on the given semaphore object and decrements it's counter by one upon returning.
dr_bool32 dr_wait_semaphore(dr_semaphore semaphore);
/// Releases the given semaphore and increments it's counter by one upon returning.
dr_bool32 dr_release_semaphore(dr_semaphore semaphore);
/////////////////////////////////////////////////////////
// Timing
typedef struct
{
dr_int64 counter;
} dr_timer;
// Initializes a high-resolution timer.
void dr_timer_init(dr_timer* pTimer);
// Ticks the timer and returns the number of seconds since the previous tick.
//
// The maximum return value is about 140 years or so.
double dr_timer_tick(dr_timer* pTimer);
/////////////////////////////////////////////////////////
// Random
// Generates a random double between 0 and 1. This is bassed of C standard rand().
double dr_randd();
// Generates a random float between 0 and 1. This is based off C standard rand().
float dr_randf();
/////////////////////////////////////////////////////////
// User Accounts and Process Management
// Retrieves the user name of the user running the application.
size_t dr_get_username(char* usernameOut, size_t usernameOutSize);
// Retrieves the ID of the current process.
unsigned int dr_get_process_id();
/////////////////////////////////////////////////////////
// Miscellaneous Stuff.
// Helper for clearing the given object to 0.
#define dr_zero_object(pObject) memset(pObject, 0, sizeof(*pObject));
// Converts an ASCII hex character to it's integral equivalent. Returns DR_FALSE if it's not a valid hex character.
dr_bool32 dr_hex_char_to_uint(char ascii, unsigned int* out);
/////////////////////////////////////////////////////////
// C++ Specific
#ifdef __cplusplus
// Use this to prevent objects of the given class or struct from being copied. This is also useful for eliminating some
// compiler warnings.
//
// Note for structs - this sets the access mode to private, so place this at the end of the declaration.
#define NO_COPY(classname) \
private: \
classname(const classname &); \
classname & operator=(const classname &);
#ifndef DR_NO_MSVC_COMPAT
extern "C++"
{
template <size_t dstSizeInBytes>
int strcpy_s(char (&dst)[dstSizeInBytes], const char* src)
{
return strcpy_s(dst, dstSizeInBytes, src);
}
template <size_t dstSizeInBytes>
int strncpy_s(char (&dst)[dstSizeInBytes], const char* src, size_t count)
{
return strncpy_s(dst, dstSizeInBytes, src, count);
}
template <size_t dstSizeInBytes>
int strcat_s(char (&dst)[dstSizeInBytes], const char* src)
{
return strcat_s(dst, dstSizeInBytes, src);
}
template <size_t dstSizeInBytes>
int strncat_s(char (&dst)[dstSizeInBytes], const char* src, size_t count)
{
return strncat_s(dst, dstSizeInBytes, src, count);
}
}
#endif
#endif
#ifdef __cplusplus
}
#endif
#endif //dr_util_h
///////////////////////////////////////////////////////////////////////////////
//
// IMPLEMENTATION
//
///////////////////////////////////////////////////////////////////////////////
#ifdef DR_IMPLEMENTATION
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h> // For memmove()
#include <errno.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <pthread.h>
#include <fcntl.h>
#include <semaphore.h>
#include <dirent.h>
#endif
int dr_strcpy_s(char* dst, size_t dstSizeInBytes, const char* src)
{
if (dst == 0) {
return EINVAL;
}
if (dstSizeInBytes == 0) {
return ERANGE;
}
if (src == 0) {
dst[0] = '\0';
return EINVAL;
}
size_t i;
for (i = 0; i < dstSizeInBytes && src[i] != '\0'; ++i) {
dst[i] = src[i];
}
if (i < dstSizeInBytes) {
dst[i] = '\0';
return 0;
}
dst[0] = '\0';
return ERANGE;
}
int dr_strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count)
{
if (dst == 0) {
return EINVAL;
}
if (dstSizeInBytes == 0) {
return ERANGE;
}
if (src == 0) {
dst[0] = '\0';
return EINVAL;
}
size_t maxcount = count;
if (count == ((size_t)-1) || count >= dstSizeInBytes) { // -1 = _TRUNCATE
maxcount = dstSizeInBytes - 1;
}
size_t i;
for (i = 0; i < maxcount && src[i] != '\0'; ++i) {
dst[i] = src[i];
}
if (src[i] == '\0' || i == count || count == ((size_t)-1)) {
dst[i] = '\0';
return 0;
}
dst[0] = '\0';
return ERANGE;
}
int dr_strcat_s(char* dst, size_t dstSizeInBytes, const char* src)
{
if (dst == 0) {
return EINVAL;
}
if (dstSizeInBytes == 0) {
return ERANGE;
}
if (src == 0) {
dst[0] = '\0';
return EINVAL;
}
char* dstorig = dst;
while (dstSizeInBytes > 0 && dst[0] != '\0') {
dst += 1;
dstSizeInBytes -= 1;
}
if (dstSizeInBytes == 0) {
return EINVAL; // Unterminated.
}
while (dstSizeInBytes > 0 && src[0] != '\0') {
*dst++ = *src++;
dstSizeInBytes -= 1;
}
if (dstSizeInBytes > 0) {
dst[0] = '\0';
} else {
dstorig[0] = '\0';
return ERANGE;
}
return 0;
}
int dr_strncat_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count)
{
if (dst == 0) {
return EINVAL;
}
if (dstSizeInBytes == 0) {
return ERANGE;
}
if (src == 0) {
return EINVAL;
}
char* dstorig = dst;
while (dstSizeInBytes > 0 && dst[0] != '\0') {
dst += 1;
dstSizeInBytes -= 1;
}
if (dstSizeInBytes == 0) {
return EINVAL; // Unterminated.
}
if (count == ((size_t)-1)) { // _TRUNCATE
count = dstSizeInBytes - 1;
}
while (dstSizeInBytes > 0 && src[0] != '\0' && count > 0)
{
*dst++ = *src++;
dstSizeInBytes -= 1;
count -= 1;
}
if (dstSizeInBytes > 0) {
dst[0] = '\0';
} else {
dstorig[0] = '\0';
return ERANGE;
}
return 0;
}
int dr_itoa_s(int value, char* dst, size_t dstSizeInBytes, int radix)
{
if (dst == NULL || dstSizeInBytes == 0) {
return EINVAL;
}
if (radix < 2 || radix > 36) {
dst[0] = '\0';
return EINVAL;
}
int sign = (value < 0 && radix == 10) ? -1 : 1; // The negative sign is only used when the base is 10.
unsigned int valueU;
if (value < 0) {
valueU = -value;
} else {
valueU = value;
}
char* dstEnd = dst;
do
{
int remainder = valueU % radix;
if (remainder > 9) {
*dstEnd = (char)((remainder - 10) + 'a');
} else {
*dstEnd = (char)(remainder + '0');
}
dstEnd += 1;
dstSizeInBytes -= 1;
valueU /= radix;
} while (dstSizeInBytes > 0 && valueU > 0);
if (dstSizeInBytes == 0) {
dst[0] = '\0';
return EINVAL; // Ran out of room in the output buffer.
}
if (sign < 0) {
*dstEnd++ = '-';
dstSizeInBytes -= 1;
}
if (dstSizeInBytes == 0) {
dst[0] = '\0';
return EINVAL; // Ran out of room in the output buffer.
}
*dstEnd = '\0';
// At this point the string will be reversed.
dstEnd -= 1;
while (dst < dstEnd) {
char temp = *dst;
*dst = *dstEnd;
*dstEnd = temp;
dst += 1;
dstEnd -= 1;
}
return 0;
}
/////////////////////////////////////////////////////////
// String Helpers
dr_bool32 dr_is_whitespace(dr_uint32 utf32)
{
return utf32 == ' ' || utf32 == '\t' || utf32 == '\n' || utf32 == '\v' || utf32 == '\f' || utf32 == '\r';
}
void dr_strrmchar(char* str, char c)
{
char* src = str;
char* dst = str;
while (src[0] != '\0')
{
dst[0] = src[0];
if (dst[0] != c) {
dst += 1;
}
src += 1;
}
dst[0] = '\0';
}
const char* dr_first_non_whitespace(const char* str)
{
if (str == NULL) {
return NULL;
}
while (str[0] != '\0' && !(str[0] != ' ' && str[0] != '\t' && str[0] != '\n' && str[0] != '\v' && str[0] != '\f' && str[0] != '\r')) {
str += 1;
}
return str;
}
const char* dr_first_whitespace(const char* str)
{
if (str == NULL) {
return NULL;
}
while (str[0] != '\0' && (str[0] != ' ' && str[0] != '\t' && str[0] != '\n' && str[0] != '\v' && str[0] != '\f' && str[0] != '\r')) {
str += 1;
}
return str;
}
const char* dr_rtrim(const char* str)
{
if (str == NULL) {
return NULL;
}
const char* rstr = str;
while (str[0] != '\0') {
if (dr_is_whitespace(str[0])) {
str += 1;
continue;
}
str += 1;
rstr = str;
}
return rstr;
}
void dr_trim(char* str)
{
if (str == NULL) {
return;
}
const char* lstr = dr_ltrim(str);
const char* rstr = dr_rtrim(lstr);
if (lstr > str) {
memmove(str, lstr, rstr-lstr);
}
str[rstr-lstr] = '\0';
}
const char* dr_next_line(const char* str)
{
if (str == NULL) {
return NULL;
}
while (str[0] != '\0' && (str[0] != '\n' && !(str[0] == '\r' && str[1] == '\n'))) {
str += 1;
}
if (str[0] == '\0') {
return NULL;
}
if (str[0] == '\r') {
return str + 2;
}
return str + 1;
}
size_t dr_copy_line(const char* str, char* lineOut, size_t lineOutSize)
{
if (str == NULL) {
return 0;
}
if (str == NULL) {
return 0;
}
size_t length = 0;
while (lineOutSize > 0 && str[0] != '\0' && (str[0] != '\n' && !(str[0] == '\r' && str[1] == '\n'))) {
*lineOut++ = *str++;
lineOutSize -= 1;
length += 1;
}
if (lineOutSize == 0) {
return 0;
}
*lineOut = '\0';
return length;
}
char* dr_string_replace(const char* src, const char* query, const char* replacement)
{
// This function could be improved, but it's good enough for now.
if (src == NULL || query == NULL) {
return NULL;
}
if (replacement == NULL) {
replacement = "";
}
int queryLen = (int)strlen(query);
int replacementLen = (int)strlen(replacement);
size_t replacementCount = 0;
const char* temp = src;
for (;;) {
temp = strstr(temp, query);
if (temp == NULL) {
break;
}
temp += queryLen;
replacementCount += 1;
}
char* result = (char*)malloc(strlen(src) + (replacementLen - queryLen)*replacementCount + 1); // +1 for null terminator.
if (result == NULL) {
return NULL;
}
char* runningResult = result;
for (size_t i = 0; i < replacementCount; ++i) {
size_t len = strstr(src, query) - src;
for (size_t j = 0; j < len; ++j) {
runningResult[j] = src[j];
}
runningResult += len;
for (int j = 0; j < replacementLen; ++j) {
runningResult[j] = replacement[j];
}
runningResult += replacementLen;
src += len + queryLen;
}
// The trailing part.
strcpy_s(runningResult, strlen(src)+1, src);
return result;
}
void dr_string_replace_ascii(char* src, char c, char replacement)
{
for (;;) {
if (*src == '\0') {
break;
}
if (*src == c) {
*src = replacement;
}
src += 1;
}
}
/////////////////////////////////////////////////////////
// Key/Value Pair Parsing
void dr_parse_key_value_pairs(dr_key_value_read_proc onRead, dr_key_value_pair_proc onPair, dr_key_value_error_proc onError, void* pUserData)
{
if (onRead == NULL) {
return;
}
char pChunk[4096];
size_t chunkSize = 0;
unsigned int currentLine = 1;
dr_bool32 moveToNextLineBeforeProcessing = DR_FALSE;
dr_bool32 skipWhitespaceBeforeProcessing = DR_FALSE;
// Just keep looping. We'll break from this loop when we have run out of data.
for (;;)
{
// Start the iteration by reading as much data as we can.
chunkSize = onRead(pUserData, pChunk, sizeof(pChunk));
if (chunkSize == 0) {
// No more data available.
return;
}
char* pChunkEnd = pChunk + chunkSize;
char* pC = pChunk; // Chunk pointer. This is as the chunk is processed.
if (moveToNextLineBeforeProcessing)
{
move_to_next_line:
while (pC < pChunkEnd && pC[0] != '\n') {
pC += 1;
}
if (pC == pChunkEnd) {
// Ran out of data. Load the next chunk and keep going.
moveToNextLineBeforeProcessing = DR_TRUE;
continue;
}
pC += 1; // pC[0] == '\n' - skip past the new line character.
currentLine += 1;
moveToNextLineBeforeProcessing = DR_FALSE;
}
if (skipWhitespaceBeforeProcessing)
{
while (pC < pChunkEnd && (pC[0] == ' ' || pC[0] == '\t' || pC[0] == '\r')) {
pC += 1;
}
if (pC == pChunkEnd) {
// Ran out of data.
skipWhitespaceBeforeProcessing = DR_TRUE;
continue;
}
skipWhitespaceBeforeProcessing = DR_FALSE;
}
// We loop character by character. When we run out of data, we start again.
while (pC < pChunkEnd)
{
//// Key ////
// Skip whitespace.
while (pC < pChunkEnd && (pC[0] == ' ' || pC[0] == '\t' || pC[0] == '\r')) {
pC += 1;
}
if (pC == pChunkEnd) {
// Ran out of data.
skipWhitespaceBeforeProcessing = DR_TRUE;
continue;
}
if (pC[0] == '\n') {
// Found the end of the line.
pC += 1;
currentLine += 1;
continue;
}
if (pC[0] == '#') {
// Found a comment. Move to the end of the line and continue.
goto move_to_next_line;
}
char* pK = pC;
while (pC < pChunkEnd && pC[0] != ' ' && pC[0] != '\t' && pC[0] != '\r' && pC[0] != '\n' && pC[0] != '#') {
pC += 1;
}
if (pC == pChunkEnd)
{
// Ran out of data. We need to move what we have of the key to the start of the chunk buffer, and then read more data.
if (chunkSize == sizeof(pChunk))
{
size_t lineSizeSoFar = pC - pK;
memmove(pChunk, pK, lineSizeSoFar);
chunkSize = lineSizeSoFar + onRead(pUserData, pChunk + lineSizeSoFar, sizeof(pChunk) - lineSizeSoFar);
pChunkEnd = pChunk + chunkSize;
pK = pChunk;
pC = pChunk + lineSizeSoFar;
while (pC < pChunkEnd && pC[0] != ' ' && pC[0] != '\t' && pC[0] != '\r' && pC[0] != '\n' && pC[0] != '#') {
pC += 1;
}
}
if (pC == pChunkEnd) {
if (chunkSize == sizeof(pChunk)) {
if (onError) {
onError(pUserData, "Line is too long. A single line cannot exceed 4KB.", currentLine);
}
goto move_to_next_line;
} else {
// No more data. Just treat this one as a value-less key and return.
if (onPair) {
pC[0] = '\0';
onPair(pUserData, pK, NULL);
}
return;
}
}
}
char* pKEnd = pC;
//// Value ////
// Skip whitespace.
while (pC < pChunkEnd && (pC[0] == ' ' || pC[0] == '\t' || pC[0] == '\r')) {
pC += 1;
}
if (pC == pChunkEnd)
{
// Ran out of data. We need to move what we have of the key to the start of the chunk buffer, and then read more data.
if (chunkSize == sizeof(pChunk))
{
size_t lineSizeSoFar = pC - pK;
memmove(pChunk, pK, lineSizeSoFar);
chunkSize = lineSizeSoFar + onRead(pUserData, pChunk + lineSizeSoFar, sizeof(pChunk) - lineSizeSoFar);
pChunkEnd = pChunk + chunkSize;
pKEnd = pChunk + (pKEnd - pK);
pK = pChunk;
pC = pChunk + lineSizeSoFar;
while (pC < pChunkEnd && (pC[0] == ' ' || pC[0] == '\t' || pC[0] == '\r')) {
pC += 1;
}
}
if (pC == pChunkEnd) {
if (chunkSize == sizeof(pChunk)) {
if (onError) {
onError(pUserData, "Line is too long. A single line cannot exceed 4KB.", currentLine);
}
goto move_to_next_line;
} else {
// No more data. Just treat this one as a value-less key and return.
if (onPair) {
pKEnd[0] = '\0';
onPair(pUserData, pK, NULL);
}
return;
}
}
}
if (pC[0] == '\n') {
// Found the end of the line. Treat it as a value-less key.
pKEnd[0] = '\0';
if (onPair) {
onPair(pUserData, pK, NULL);
}
pC += 1;
currentLine += 1;
continue;
}
if (pC[0] == '#') {
// Found a comment. Treat is as a value-less key and move to the end of the line.
pKEnd[0] = '\0';
if (onPair) {
onPair(pUserData, pK, NULL);
}
goto move_to_next_line;
}
char* pV = pC;
// Find the last non-whitespace character.
char* pVEnd = pC;
while (pC < pChunkEnd && pC[0] != '\n' && pC[0] != '#') {
if (pC[0] != ' ' && pC[0] != '\t' && pC[0] != '\r') {
pVEnd = pC;
}
pC += 1;
}
if (pC == pChunkEnd)
{
// Ran out of data. We need to move what we have of the key to the start of the chunk buffer, and then read more data.
if (chunkSize == sizeof(pChunk))
{
size_t lineSizeSoFar = pC - pK;
memmove(pChunk, pK, lineSizeSoFar);
chunkSize = lineSizeSoFar + onRead(pUserData, pChunk + lineSizeSoFar, sizeof(pChunk) - lineSizeSoFar);
pChunkEnd = pChunk + chunkSize;
pVEnd = pChunk + (pVEnd - pK);
pKEnd = pChunk + (pKEnd - pK);
pV = pChunk + (pV - pK);
pK = pChunk;
pC = pChunk + lineSizeSoFar;
while (pC < pChunkEnd && pC[0] != '\n' && pC[0] != '#') {
if (pC[0] != ' ' && pC[0] != '\t' && pC[0] != '\r') {
pVEnd = pC;
}
pC += 1;
}
}
if (pC == pChunkEnd) {
if (chunkSize == sizeof(pChunk)) {
if (onError) {
onError(pUserData, "Line is too long. A single line cannot exceed 4KB.", currentLine);
}
goto move_to_next_line;
}
}
}
// Before null-terminating the value we first need to determine how we'll proceed after posting onPair.
dr_bool32 wasOnNL = pVEnd[1] == '\n';
pKEnd[0] = '\0';
pVEnd[1] = '\0';
if (onPair) {
onPair(pUserData, pK, pV);
}
if (wasOnNL)
{
// Was sitting on a new-line character.
pC += 1;
currentLine += 1;
continue;
}
else
{
// Was sitting on a comment - just to the next line.
goto move_to_next_line;
}
}
}
}
typedef struct
{
FILE* pFile;
dr_key_value_pair_proc onPair;
dr_key_value_error_proc onError;
void* pOriginalUserData;
} dr_parse_key_value_pairs_from_file_data;
size_t dr_parse_key_value_pairs_from_file__on_read(void* pUserData, void* pDataOut, size_t bytesToRead)
{
dr_parse_key_value_pairs_from_file_data* pData = (dr_parse_key_value_pairs_from_file_data*)pUserData;
assert(pData != NULL);
return fread(pDataOut, 1, bytesToRead, pData->pFile);
}
void dr_parse_key_value_pairs_from_file__on_pair(void* pUserData, const char* key, const char* value)
{
dr_parse_key_value_pairs_from_file_data* pData = (dr_parse_key_value_pairs_from_file_data*)pUserData;
assert(pData != NULL);
pData->onPair(pData->pOriginalUserData, key, value);
}
void dr_parse_key_value_pairs_from_file__on_error(void* pUserData, const char* message, unsigned int line)
{
dr_parse_key_value_pairs_from_file_data* pData = (dr_parse_key_value_pairs_from_file_data*)pUserData;
assert(pData != NULL);
pData->onError(pData->pOriginalUserData, message, line);
}
dr_bool32 dr_parse_key_value_pairs_from_file(const char* filePath, dr_key_value_pair_proc onPair, dr_key_value_error_proc onError, void* pUserData)
{
dr_parse_key_value_pairs_from_file_data data;
data.pFile = dr_fopen(filePath, "rb");
if (data.pFile == NULL) {
if (onError) onError(pUserData, "Could not open file.", 0);
return DR_FALSE;
}
data.onPair = onPair;
data.onError = onError;
data.pOriginalUserData = pUserData;
dr_parse_key_value_pairs(dr_parse_key_value_pairs_from_file__on_read, dr_parse_key_value_pairs_from_file__on_pair, dr_parse_key_value_pairs_from_file__on_error, &data);
fclose(data.pFile);
return DR_TRUE;
}
/////////////////////////////////////////////////////////
// Basic Tokenizer
const char* dr_next_token(const char* tokens, char* tokenOut, size_t tokenOutSize)
{
if (tokenOut) tokenOut[0] = '\0';
if (tokens == NULL) {
return NULL;
}
// Skip past leading whitespace.
while (tokens[0] != '\0' && !(tokens[0] != ' ' && tokens[0] != '\t' && tokens[0] != '\n' && tokens[0] != '\v' && tokens[0] != '\f' && tokens[0] != '\r')) {
tokens += 1;
}
if (tokens[0] == '\0') {
return NULL;
}
const char* strBeg = tokens;
const char* strEnd = strBeg;
if (strEnd[0] == '\"')
{
// It's double-quoted - loop until the next unescaped quote character.
// Skip past the first double-quote character.
strBeg += 1;
strEnd += 1;
// Keep looping until the next unescaped double-quote character.
char prevChar = '\0';
while (strEnd[0] != '\0' && (strEnd[0] != '\"' || prevChar == '\\'))
{
prevChar = strEnd[0];
strEnd += 1;
}
}
else
{
// It's not double-quoted - just loop until the first whitespace.
while (strEnd[0] != '\0' && (strEnd[0] != ' ' && strEnd[0] != '\t' && strEnd[0] != '\n' && strEnd[0] != '\v' && strEnd[0] != '\f' && strEnd[0] != '\r')) {
strEnd += 1;
}
}
// If the output buffer is large enough to hold the token, copy the token into it. When we copy the token we need to
// ensure we don't include the escape character.
//assert(strEnd >= strBeg);
while (tokenOutSize > 1 && strBeg < strEnd)
{
if (strBeg[0] == '\\' && strBeg[1] == '\"' && strBeg < strEnd) {
strBeg += 1;
}
*tokenOut++ = *strBeg++;
tokenOutSize -= 1;
}
// Null-terminate.
if (tokenOutSize > 0) {
*tokenOut = '\0';
}
// Skip past the double-quote character before returning.
if (strEnd[0] == '\"') {
strEnd += 1;
}
return strEnd;
}
/////////////////////////////////////////////////////////
// Known Folders
#if defined(_WIN32)
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable:4091) // 'typedef ': ignored on left of 'tagGPFIDL_FLAGS' when no variable is declared
#endif
#include <shlobj.h>
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
dr_bool32 dr_get_executable_path(char* pathOut, size_t pathOutSize)
{
if (pathOut == NULL || pathOutSize == 0) {
return 0;
}
DWORD length = GetModuleFileNameA(NULL, pathOut, (DWORD)pathOutSize);
if (length == 0) {
pathOut[0] = '\0';
return DR_FALSE;
}
// Force null termination.
if (length == pathOutSize) {
pathOut[length - 1] = '\0';
}
// Back slashes need to be normalized to forward.
while (pathOut[0] != '\0') {
if (pathOut[0] == '\\') {
pathOut[0] = '/';
}
pathOut += 1;
}
return DR_TRUE;
}
dr_bool32 dr_get_config_folder_path(char* pathOut, size_t pathOutSize)
{
// The documentation for SHGetFolderPathA() says that the output path should be the size of MAX_PATH. We'll enforce
// that just to be safe.
if (pathOutSize >= MAX_PATH)
{
SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, pathOut);
}
else
{
char pathOutTemp[MAX_PATH];
SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, pathOutTemp);
if (strcpy_s(pathOut, pathOutSize, pathOutTemp) != 0) {
return 0;
}
}
// Back slashes need to be normalized to forward.
while (pathOut[0] != '\0') {
if (pathOut[0] == '\\') {
pathOut[0] = '/';
}
pathOut += 1;
}
return 1;
}
dr_bool32 dr_get_log_folder_path(char* pathOut, size_t pathOutSize)
{
return dr_get_config_folder_path(pathOut, pathOutSize);
}
const char* dr_get_current_directory(char* pathOut, size_t pathOutSize)
{
DWORD result = GetCurrentDirectoryA((DWORD)pathOutSize, pathOut);
if (result == 0) {
return NULL;
}
return pathOut;
}
dr_bool32 dr_set_current_directory(const char* path)
{
return SetCurrentDirectoryA(path) != 0;
}
#else
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
dr_bool32 dr_get_executable_path(char* pathOut, size_t pathOutSize)
{
if (pathOut == NULL || pathOutSize == 0) {
return 0;
}
ssize_t length = readlink("/proc/self/exe", pathOut, pathOutSize);
if (length == -1) {
pathOut[0] = '\0';
return DR_FALSE;
}
if ((size_t)length == pathOutSize) {
pathOut[length - 1] = '\0';
} else {
pathOut[length] = '\0';
}
return DR_TRUE;
}
dr_bool32 dr_get_config_folder_path(char* pathOut, size_t pathOutSize)
{
const char* configdir = getenv("XDG_CONFIG_HOME");
if (configdir != NULL)
{
return strcpy_s(pathOut, pathOutSize, configdir) == 0;
}
else
{
const char* homedir = getenv("HOME");
if (homedir == NULL) {
homedir = getpwuid(getuid())->pw_dir;
}
if (homedir != NULL)
{
if (strcpy_s(pathOut, pathOutSize, homedir) == 0)
{
size_t homedirLength = strlen(homedir);
pathOut += homedirLength;
pathOutSize -= homedirLength;
if (pathOutSize > 0)
{
pathOut[0] = '/';
pathOut += 1;
pathOutSize -= 1;
return strcpy_s(pathOut, pathOutSize, ".config") == 0;
}
}
}
}
return 0;
}
dr_bool32 dr_get_log_folder_path(char* pathOut, size_t pathOutSize)
{
return strcpy_s(pathOut, pathOutSize, "var/log") == 0;
}
const char* dr_get_current_directory(char* pathOut, size_t pathOutSize)
{
return getcwd(pathOut, pathOutSize);
}
dr_bool32 dr_set_current_directory(const char* path)
{
return chdir(path) == 0;
}
#endif
dr_bool32 dr_get_executable_directory_path(char* pathOut, size_t pathOutSize)
{
if (!dr_get_executable_path(pathOut, pathOutSize)) {
return DR_FALSE;
}
// A null terminator needs to be placed at the last slash.
char* lastSlash = pathOut;
while (pathOut[0] != '\0') {
if (pathOut[0] == '/' || pathOut[0] == '\\') {
lastSlash = pathOut;
}
pathOut += 1;
}
lastSlash[0] = '\0';
return DR_TRUE;
}
/////////////////////////////////////////////////////////
// Basic File Management
#ifndef _WIN32
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#endif
FILE* dr_fopen(const char* filePath, const char* openMode)
{
FILE* pFile;
#ifdef _MSC_VER
if (fopen_s(&pFile, filePath, openMode) != 0) {
return NULL;
}
#else
pFile = fopen(filePath, openMode);
if (pFile == NULL) {
return NULL;
}
#endif
return pFile;
}
dr_bool32 dr_create_empty_file(const char* fileName, dr_bool32 failIfExists)
{
if (fileName == NULL) {
return DR_FALSE;
}
#ifdef _WIN32
DWORD dwCreationDisposition;
if (failIfExists) {
dwCreationDisposition = CREATE_NEW;
} else {
dwCreationDisposition = CREATE_ALWAYS;
}
HANDLE hFile = CreateFileA(fileName, FILE_GENERIC_WRITE, 0, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
return DR_FALSE;
}
CloseHandle(hFile);
return DR_TRUE;
#else
int flags = O_WRONLY | O_CREAT;
if (failIfExists) {
flags |= O_EXCL;
} else {
flags |= O_TRUNC;
}
int fd = open(fileName, flags, 0666);
if (fd == -1) {
return DR_FALSE;
}
close(fd);
return DR_TRUE;
#endif
}
static void* dr_open_and_read_file_with_extra_data(const char* filePath, size_t* pFileSizeOut, size_t extraBytes)
{
if (pFileSizeOut) *pFileSizeOut = 0; // For safety.
if (filePath == NULL) {
return NULL;
}
// TODO: Use 64-bit versions of the FILE APIs.
FILE* pFile = dr_fopen(filePath, "rb");
if (pFile == NULL) {
return NULL;
}
fseek(pFile, 0, SEEK_END);
dr_uint64 fileSize = ftell(pFile);
fseek(pFile, 0, SEEK_SET);
if (fileSize + extraBytes > SIZE_MAX) {
fclose(pFile);
return NULL; // File is too big.
}
void* pFileData = malloc((size_t)fileSize + extraBytes); // <-- Safe cast due to the check above.
if (pFileData == NULL) {
fclose(pFile);
return NULL; // Failed to allocate memory for the file. Good chance the file is too big.
}
size_t bytesRead = fread(pFileData, 1, (size_t)fileSize, pFile);
if (bytesRead != fileSize) {
free(pFileData);
fclose(pFile);
return NULL; // Failed to read every byte from the file.
}
fclose(pFile);
if (pFileSizeOut) *pFileSizeOut = (size_t)fileSize;
return pFileData;
}
void* dr_open_and_read_file(const char* filePath, size_t* pFileSizeOut)
{
return dr_open_and_read_file_with_extra_data(filePath, pFileSizeOut, 0);
}
char* dr_open_and_read_text_file(const char* filePath, size_t* pFileSizeOut)
{
if (pFileSizeOut) *pFileSizeOut = 0; // For safety.
size_t fileSize;
char* pFileData = (char*)dr_open_and_read_file_with_extra_data(filePath, &fileSize, 1); // <-- 1 extra byte for the null terminator.
if (pFileData == NULL) {
return NULL;
}
pFileData[fileSize] = '\0';
if (pFileSizeOut) *pFileSizeOut = fileSize;
return pFileData;
}
dr_bool32 dr_open_and_write_file(const char* filePath, const void* pData, size_t dataSize)
{
if (filePath == NULL) {
return DR_FALSE;
}
// TODO: Use 64-bit versions of the FILE APIs.
FILE* pFile = dr_fopen(filePath, "wb");
if (pFile == NULL) {
return DR_FALSE;
}
if (pData != NULL && dataSize > 0) {
fwrite(pData, 1, dataSize, pFile);
}
fclose(pFile);
return DR_TRUE;
}
dr_bool32 dr_open_and_write_text_file(const char* filePath, const char* text)
{
if (text == NULL) {
text = "";
}
return dr_open_and_write_file(filePath, text, strlen(text));
}
void dr_free_file_data(void* valueReturnedByOpenAndReadFile)
{
free(valueReturnedByOpenAndReadFile);
}
dr_bool32 dr_file_exists(const char* filePath)
{
if (filePath == NULL) {
return DR_FALSE;
}
#if _WIN32
DWORD attributes = GetFileAttributesA(filePath);
return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
#else
struct stat info;
if (stat(filePath, &info) != 0) {
return DR_FALSE; // Likely the folder doesn't exist.
}
return (info.st_mode & S_IFDIR) == 0;
#endif
}
dr_bool32 dr_directory_exists(const char* directoryPath)
{
if (directoryPath == NULL) {
return DR_FALSE;
}
#if _WIN32
DWORD attributes = GetFileAttributesA(directoryPath);
return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
#else
struct stat info;
if (stat(directoryPath, &info) != 0) {
return DR_FALSE; // Likely the folder doesn't exist.
}
return (info.st_mode & S_IFDIR) != 0;
#endif
}
dr_bool32 dr_move_file(const char* oldPath, const char* newPath)
{
if (oldPath == NULL || newPath == NULL) {
return DR_FALSE;
}
#if _WIN32
return MoveFileExA(oldPath, newPath, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH) != 0;
#else
return rename(oldPath, newPath) == 0;
#endif
}
dr_bool32 dr_copy_file(const char* srcPath, const char* dstPath, dr_bool32 failIfExists)
{
if (srcPath == NULL || dstPath == NULL) {
return DR_FALSE;
}
#if _WIN32
return CopyFileA(srcPath, dstPath, failIfExists) != 0;
#else
int fdSrc = open(srcPath, O_RDONLY, 0666);
if (fdSrc == -1) {
return DR_FALSE;
}
int fdDst = open(dstPath, O_WRONLY | O_TRUNC | O_CREAT | ((failIfExists) ? O_EXCL : 0), 0666);
if (fdDst == -1) {
close(fdSrc);
return DR_FALSE;
}
dr_bool32 result = DR_TRUE;
struct stat info;
if (fstat(fdSrc, &info) == 0) {
char buffer[BUFSIZ];
int bytesRead;
while ((bytesRead = read(fdSrc, buffer, sizeof(buffer))) > 0) {
if (write(fdDst, buffer, bytesRead) != bytesRead) {
result = DR_FALSE;
break;
}
}
} else {
result = DR_FALSE;
}
close(fdDst);
close(fdSrc);
// Permissions.
chmod(dstPath, info.st_mode & 07777);
return result;
#endif
}
dr_bool32 dr_is_file_read_only(const char* filePath)
{
if (filePath == NULL || filePath[0] == '\0') {
return DR_FALSE;
}
#if _WIN32
DWORD attributes = GetFileAttributesA(filePath);
return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_READONLY) != 0;
#else
return access(filePath, W_OK) == -1;
#endif
}
dr_uint64 dr_get_file_modified_time(const char* filePath)
{
if (filePath == NULL || filePath[0] == '\0') {
return 0;
}
#if _WIN32
HANDLE hFile = CreateFileA(filePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
return 0;
}
FILETIME fileTime;
BOOL wasSuccessful = GetFileTime(hFile, NULL, NULL, &fileTime);
CloseHandle(hFile);
if (!wasSuccessful) {
return 0;
}
ULARGE_INTEGER result;
result.HighPart = fileTime.dwHighDateTime;
result.LowPart = fileTime.dwLowDateTime;
return result.QuadPart;
#else
struct stat info;
if (stat(filePath, &info) != 0) {
return 0;
}
return info.st_mtime;
#endif
}
dr_bool32 dr_delete_file(const char* filePath)
{
if (filePath == NULL) {
return DR_FALSE;
}
#if _WIN32
return DeleteFileA(filePath) != 0;
#else
return remove(filePath) == 0;
#endif
}
dr_bool32 dr_mkdir(const char* directoryPath)
{
if (directoryPath == NULL) {
return DR_FALSE;
}
#if _WIN32
return CreateDirectoryA(directoryPath, NULL) != 0;
#else
return mkdir(directoryPath, 0777) == 0;
#endif
}
dr_bool32 dr_mkdir_recursive(const char* directoryPath)
{
if (directoryPath == NULL || directoryPath[0] == '\0') {
return DR_FALSE;
}
// All we need to do is iterate over every segment in the path and try creating the directory.
char runningPath[4096];
memset(runningPath, 0, sizeof(runningPath));
size_t i = 0;
for (;;) {
if (i >= sizeof(runningPath)-1) {
return DR_FALSE; // Path is too long.
}
if (directoryPath[0] == '\0' || directoryPath[0] == '/' || directoryPath[0] == '\\') {
if (runningPath[0] != '\0' && !(runningPath[1] == ':' && runningPath[2] == '\0')) { // <-- If the running path is empty, it means we're trying to create the root directory.
if (!dr_directory_exists(runningPath)) {
if (!dr_mkdir(runningPath)) {
return DR_FALSE;
}
}
}
//printf("%s\n", runningPath);
runningPath[i++] = '/';
runningPath[i] = '\0';
if (directoryPath[0] == '\0') {
break;
}
} else {
runningPath[i++] = directoryPath[0];
}
directoryPath += 1;
}
return DR_TRUE;
}
dr_bool32 dr_iterate_files(const char* directory, dr_bool32 recursive, dr_iterate_files_proc proc, void* pUserData)
{
#ifdef _WIN32
char searchQuery[MAX_PATH];
strcpy_s(searchQuery, sizeof(searchQuery), directory);
unsigned int searchQueryLength = (unsigned int)strlen(searchQuery);
if (searchQueryLength >= MAX_PATH - 3) {
return DR_FALSE; // Path is too long.
}
searchQuery[searchQueryLength + 0] = '\\';
searchQuery[searchQueryLength + 1] = '*';
searchQuery[searchQueryLength + 2] = '\0';
WIN32_FIND_DATAA ffd;
HANDLE hFind = FindFirstFileA(searchQuery, &ffd);
if (hFind == INVALID_HANDLE_VALUE) {
return DR_FALSE; // Failed to begin search.
}
do
{
// Skip past "." and ".." directories.
if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) {
continue;
}
char filePath[MAX_PATH];
strcpy_s(filePath, sizeof(filePath), directory);
strcat_s(filePath, sizeof(filePath), "/");
strcat_s(filePath, sizeof(filePath), ffd.cFileName);
if (!proc(filePath, pUserData)) {
return DR_FALSE;
}
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (recursive) {
if (!dr_iterate_files(filePath, recursive, proc, pUserData)) {
return DR_FALSE;
}
}
}
} while (FindNextFileA(hFind, &ffd));
FindClose(hFind);
#else
DIR* dir = opendir(directory);
if (dir == NULL) {
return DR_FALSE;
}
struct dirent* info = NULL;
while ((info = readdir(dir)) != NULL)
{
// Skip past "." and ".." directories.
if (strcmp(info->d_name, ".") == 0 || strcmp(info->d_name, "..") == 0) {
continue;
}
char filePath[4096];
strcpy_s(filePath, sizeof(filePath), directory);
strcat_s(filePath, sizeof(filePath), "/");
strcat_s(filePath, sizeof(filePath), info->d_name);
struct stat fileinfo;
if (stat(filePath, &fileinfo) != 0) {
continue;
}
if (!proc(filePath, pUserData)) {
return DR_FALSE;
}
if (fileinfo.st_mode & S_IFDIR) {
if (recursive) {
if (!dr_iterate_files(filePath, recursive, proc, pUserData)) {
return DR_FALSE;
}
}
}
}
closedir(dir);
#endif
return DR_TRUE;
}
/////////////////////////////////////////////////////////
// DPI Awareness
#if defined(_WIN32) || defined(_WIN64)
typedef enum PROCESS_DPI_AWARENESS {
PROCESS_DPI_UNAWARE = 0,
PROCESS_SYSTEM_DPI_AWARE = 1,
PROCESS_PER_MONITOR_DPI_AWARE = 2
} PROCESS_DPI_AWARENESS;
typedef enum MONITOR_DPI_TYPE {
MDT_EFFECTIVE_DPI = 0,
MDT_ANGULAR_DPI = 1,
MDT_RAW_DPI = 2,
MDT_DEFAULT = MDT_EFFECTIVE_DPI
} MONITOR_DPI_TYPE;
typedef BOOL (__stdcall * PFN_SetProcessDPIAware) (void);
typedef HRESULT (__stdcall * PFN_SetProcessDpiAwareness) (PROCESS_DPI_AWARENESS);
typedef HRESULT (__stdcall * PFN_GetDpiForMonitor) (HMONITOR hmonitor, MONITOR_DPI_TYPE dpiType, UINT *dpiX, UINT *dpiY);
void dr_win32_make_dpi_aware()
{
dr_bool32 fallBackToDiscouragedAPI = DR_FALSE;
// We can't call SetProcessDpiAwareness() directly because otherwise on versions of Windows < 8.1 we'll get an error at load time about
// a missing DLL.
HMODULE hSHCoreDLL = LoadLibraryW(L"shcore.dll");
if (hSHCoreDLL != NULL)
{
PFN_SetProcessDpiAwareness _SetProcessDpiAwareness = (PFN_SetProcessDpiAwareness)GetProcAddress(hSHCoreDLL, "SetProcessDpiAwareness");
if (_SetProcessDpiAwareness != NULL)
{
if (_SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE) != S_OK)
{
fallBackToDiscouragedAPI = DR_FALSE;
}
}
else
{
fallBackToDiscouragedAPI = DR_FALSE;
}
FreeLibrary(hSHCoreDLL);
}
else
{
fallBackToDiscouragedAPI = DR_FALSE;
}
if (fallBackToDiscouragedAPI)
{
HMODULE hUser32DLL = LoadLibraryW(L"user32.dll");
if (hUser32DLL != NULL)
{
PFN_SetProcessDPIAware _SetProcessDPIAware = (PFN_SetProcessDPIAware)GetProcAddress(hUser32DLL, "SetProcessDPIAware");
if (_SetProcessDPIAware != NULL) {
_SetProcessDPIAware();
}
FreeLibrary(hUser32DLL);
}
}
}
void dr_win32_get_base_dpi(int* pDPIXOut, int* pDPIYOut)
{
if (pDPIXOut != NULL) {
*pDPIXOut = 96;
}
if (pDPIYOut != NULL) {
*pDPIYOut = 96;
}
}
void dr_win32_get_system_dpi(int* pDPIXOut, int* pDPIYOut)
{
if (pDPIXOut != NULL) {
*pDPIXOut = GetDeviceCaps(GetDC(NULL), LOGPIXELSX);
}
if (pDPIYOut != NULL) {
*pDPIYOut = GetDeviceCaps(GetDC(NULL), LOGPIXELSY);
}
}
typedef struct
{
int monitorIndex;
int i;
int dpiX;
int dpiY;
PFN_GetDpiForMonitor _GetDpiForMonitor;
} win32_get_monitor_dpi_data;
static BOOL CALLBACK win32_get_monitor_dpi_callback(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
(void)hdcMonitor;
(void)lprcMonitor;
win32_get_monitor_dpi_data* pData = (win32_get_monitor_dpi_data*)dwData;
if (pData->monitorIndex == pData->i)
{
UINT dpiX;
UINT dpiY;
if (pData->_GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY) == S_OK)
{
pData->dpiX = (int)dpiX;
pData->dpiY = (int)dpiY;
}
else
{
dr_win32_get_system_dpi(&pData->dpiX, &pData->dpiY);
}
return FALSE; // Return DR_FALSE to terminate the enumerator.
}
pData->i += 1;
return TRUE;
}
void dr_win32_get_monitor_dpi(int monitor, int* pDPIXOut, int* pDPIYOut)
{
// If multi-monitor DPI awareness is not supported we will need to fall back to system DPI.
HMODULE hSHCoreDLL = LoadLibraryW(L"shcore.dll");
if (hSHCoreDLL == NULL) {
dr_win32_get_system_dpi(pDPIXOut, pDPIYOut);
return;
}
PFN_GetDpiForMonitor _GetDpiForMonitor = (PFN_GetDpiForMonitor)GetProcAddress(hSHCoreDLL, "GetDpiForMonitor");
if (_GetDpiForMonitor == NULL) {
dr_win32_get_system_dpi(pDPIXOut, pDPIYOut);
FreeLibrary(hSHCoreDLL);
return;
}
win32_get_monitor_dpi_data data;
data.monitorIndex = monitor;
data.i = 0;
data.dpiX = 0;
data.dpiY = 0;
data._GetDpiForMonitor = _GetDpiForMonitor;
EnumDisplayMonitors(NULL, NULL, win32_get_monitor_dpi_callback, (LPARAM)&data);
if (pDPIXOut) {
*pDPIXOut = data.dpiX;
}
if (pDPIYOut) {
*pDPIYOut = data.dpiY;
}
FreeLibrary(hSHCoreDLL);
}
static BOOL CALLBACK win32_get_monitor_count_callback(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
(void)hMonitor;
(void)hdcMonitor;
(void)lprcMonitor;
int *count = (int*)dwData;
(*count)++;
return TRUE;
}
int dr_win32_get_monitor_count()
{
int count = 0;
if (EnumDisplayMonitors(NULL, NULL, win32_get_monitor_count_callback, (LPARAM)&count)) {
return count;
}
return 0;
}
#endif
/////////////////////////////////////////////////////////
// Date / Time
time_t dr_now()
{
return time(NULL);
}
void dr_datetime_short(time_t t, char* strOut, unsigned int strOutSize)
{
#if defined(_MSC_VER)
struct tm local;
localtime_s(&local, &t);
strftime(strOut, strOutSize, "%x %H:%M:%S", &local);
#else
struct tm *local = localtime(&t);
strftime(strOut, strOutSize, "%x %H:%M:%S", local);
#endif
}
void dr_date_YYYYMMDD(time_t t, char* strOut, unsigned int strOutSize)
{
#if defined(_MSC_VER)
struct tm local;
localtime_s(&local, &t);
strftime(strOut, strOutSize, "%Y%m%d", &local);
#else
struct tm *local = localtime(&t);
strftime(strOut, strOutSize, "%Y%m%d", local);
#endif
}
/////////////////////////////////////////////////////////
// Command Line
typedef struct
{
dr_cmdline* pCmdLine;
char* value;
// Win32 style data.
char* win32_payload;
char* valueEnd;
// argv style data.
int iarg; // <-- This starts at -1 so that the first call to next() increments it to 0.
} dr_cmdline_iterator;
dr_cmdline_iterator dr_cmdline_begin(dr_cmdline* pCmdLine)
{
dr_cmdline_iterator i;
i.pCmdLine = pCmdLine;
i.value = NULL;
i.win32_payload = NULL;
i.valueEnd = NULL;
i.iarg = -1;
if (pCmdLine != NULL && pCmdLine->win32 != NULL) {
// Win32 style
size_t length = strlen(pCmdLine->win32);
i.win32_payload = (char*)malloc(length + 2); // +2 for a double null terminator.
strcpy_s(i.win32_payload, length + 2, pCmdLine->win32);
i.win32_payload[length + 1] = '\0';
i.valueEnd = i.win32_payload;
}
return i;
}
dr_bool32 dr_cmdline_next(dr_cmdline_iterator* i)
{
if (i != NULL && i->pCmdLine != NULL)
{
if (i->pCmdLine->win32 != NULL)
{
// Win32 style
if (i->value == NULL) {
i->value = i->win32_payload;
i->valueEnd = i->value;
} else {
i->value = i->valueEnd + 1;
}
// Move to the start of the next argument.
while (i->value[0] == ' ') {
i->value += 1;
}
// If at this point we are sitting on the null terminator it means we have finished iterating.
if (i->value[0] == '\0')
{
free(i->win32_payload);
i->win32_payload = NULL;
i->pCmdLine = NULL;
i->value = NULL;
i->valueEnd = NULL;
return DR_FALSE;
}
// Move to the end of the token. If the argument begins with a double quote, we iterate until we find
// the next unescaped double-quote.
if (i->value[0] == '\"')
{
// Go to the last unescaped double-quote.
i->value += 1;
i->valueEnd = i->value + 1;
while (i->valueEnd[0] != '\0' && i->valueEnd[0] != '\"')
{
if (i->valueEnd[0] == '\\') {
i->valueEnd += 1;
if (i->valueEnd[0] == '\0') {
break;
}
}
i->valueEnd += 1;
}
i->valueEnd[0] = '\0';
}
else
{
// Go to the next space.
i->valueEnd = i->value + 1;
while (i->valueEnd[0] != '\0' && i->valueEnd[0] != ' ')
{
i->valueEnd += 1;
}
i->valueEnd[0] = '\0';
}
return DR_TRUE;
}
else
{
// argv style
i->iarg += 1;
if (i->iarg < i->pCmdLine->argc)
{
i->value = i->pCmdLine->argv[i->iarg];
return DR_TRUE;
}
else
{
i->value = NULL;
return DR_FALSE;
}
}
}
return DR_FALSE;
}
dr_bool32 dr_init_cmdline(dr_cmdline* pCmdLine, int argc, char** argv)
{
if (pCmdLine == NULL) {
return DR_FALSE;
}
pCmdLine->argc = argc;
pCmdLine->argv = argv;
pCmdLine->win32 = NULL;
return DR_TRUE;
}
dr_bool32 dr_init_cmdline_win32(dr_cmdline* pCmdLine, const char* args)
{
if (pCmdLine == NULL) {
return DR_FALSE;
}
pCmdLine->argc = 0;
pCmdLine->argv = NULL;
pCmdLine->win32 = args;
return DR_TRUE;
}
void dr_parse_cmdline(dr_cmdline* pCmdLine, dr_cmdline_parse_proc callback, void* pUserData)
{
if (pCmdLine == NULL || callback == NULL) {
return;
}
char pTemp[2] = {0};
char* pKey = NULL;
char* pVal = NULL;
dr_cmdline_iterator arg = dr_cmdline_begin(pCmdLine);
if (dr_cmdline_next(&arg))
{
if (!callback("[path]", arg.value, pUserData)) {
return;
}
}
while (dr_cmdline_next(&arg))
{
if (arg.value[0] == '-')
{
// key
// If the key is non-null, but the value IS null, it means we hit a key with no value in which case it will not yet have been posted.
if (pKey != NULL && pVal == NULL)
{
if (!callback(pKey, pVal, pUserData)) {
return;
}
pKey = NULL;
}
else
{
// Need to ensure the key and value are reset before doing any further processing.
pKey = NULL;
pVal = NULL;
}
if (arg.value[1] == '-')
{
// --argument style
pKey = arg.value + 2;
}
else
{
// -a -b -c -d or -abcd style
if (arg.value[1] != '\0')
{
if (arg.value[2] == '\0')
{
// -a -b -c -d style
pTemp[0] = arg.value[1];
pKey = pTemp;
pVal = NULL;
}
else
{
// -abcd style.
int i = 1;
while (arg.value[i] != '\0')
{
pTemp[0] = arg.value[i];
if (!callback(pTemp, NULL, pUserData)) {
return;
}
pKey = NULL;
pVal = NULL;
i += 1;
}
}
}
}
}
else
{
// value
pVal = arg.value;
if (!callback(pKey, pVal, pUserData)) {
return;
}
}
}
// There may be a key without a value that needs posting.
if (pKey != NULL && pVal == NULL) {
callback(pKey, pVal, pUserData);
}
}
typedef struct
{
dr_bool32 exists;
const char* key;
} dr_cmdline_key_exists_data;
dr_bool32 dr_cmdline_key_exists_callback(const char* key, const char* value, void* pUserData)
{
(void)value;
dr_cmdline_key_exists_data* pData = (dr_cmdline_key_exists_data*)pUserData;
assert(pData != NULL);
if (key != NULL && strcmp(pData->key, key) == 0) {
pData->exists = DR_TRUE;
return DR_FALSE;
}
return DR_TRUE;
}
dr_bool32 dr_cmdline_key_exists(dr_cmdline* pCmdLine, const char* key)
{
dr_cmdline_key_exists_data data;
data.exists = DR_FALSE;
data.key = key;
dr_parse_cmdline(pCmdLine, dr_cmdline_key_exists_callback, &data);
return data.exists;
}
int dr_cmdline_to_argv(dr_cmdline* pCmdLine, char*** argvOut)
{
if (argvOut == NULL) return 0;
*argvOut = NULL; // Safety.
int argc = 0;
char** argv = NULL;
size_t cmdlineLen = 0;
// The command line is parsed in 2 passes. The first pass simple calculates the required sizes of each buffer. The second
// pass fills those buffers with actual data.
// First pass.
dr_cmdline_iterator arg = dr_cmdline_begin(pCmdLine);
while (dr_cmdline_next(&arg)) {
cmdlineLen += strlen(arg.value) + 1; // +1 for null terminator.
argc += 1;
}
if (argc == 0) {
return 0;
}
// The entire data for the command line is stored in a single buffer.
char* data = (char*)malloc((argc * sizeof(char**)) + (cmdlineLen * sizeof(char)));
if (data == NULL) {
return 0; // Ran out of memory.
}
argv = (char**)data;
char* cmdlineStr = data + (argc * sizeof(char**));
// Second pass.
argc = 0;
cmdlineLen = 0;
arg = dr_cmdline_begin(pCmdLine);
while (dr_cmdline_next(&arg)) {
argv[argc] = cmdlineStr + cmdlineLen;
int i = 0;
while (arg.value[i] != '\0') {
argv[argc][i] = arg.value[i];
i += 1;
}
argv[argc][i] = '\0';
cmdlineLen += strlen(arg.value) + 1; // +1 for null terminator.
argc += 1;
}
*argvOut = argv;
return argc;
}
int dr_winmain_to_argv(const char* cmdlineWinMain, char*** argvOut)
{
dr_cmdline cmdline;
if (!dr_init_cmdline_win32(&cmdline, cmdlineWinMain)) {
return 0;
}
return dr_cmdline_to_argv(&cmdline, argvOut);
}
void dr_free_argv(char** argv)
{
if (argv == NULL) {
return;
}
free(argv);
}
/////////////////////////////////////////////////////////
// Threading
#if defined(_WIN32)
void dr_sleep(unsigned int milliseconds)
{
Sleep((DWORD)milliseconds);
}
void dr_yield()
{
SwitchToThread();
}
unsigned int dr_get_logical_processor_count()
{
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
return (unsigned int)sysinfo.dwNumberOfProcessors;
}
typedef struct
{
/// The Win32 thread handle.
HANDLE hThread;
/// The entry point.
dr_thread_entry_proc entryProc;
/// The user data to pass to the thread's entry point.
void* pData;
/// Set to DR_TRUE by the entry function. We use this to wait for the entry function to start.
dr_bool32 isInEntryProc;
} dr_thread_win32;
static DWORD WINAPI dr_thread_entry_proc_win32(LPVOID pUserData)
{
dr_thread_win32* pThreadWin32 = (dr_thread_win32*)pUserData;
assert(pThreadWin32 != NULL);
void* pEntryProcData = pThreadWin32->pData;
dr_thread_entry_proc entryProc = pThreadWin32->entryProc;
assert(entryProc != NULL);
pThreadWin32->isInEntryProc = DR_TRUE;
return (DWORD)entryProc(pEntryProcData);
}
dr_thread dr_create_thread(dr_thread_entry_proc entryProc, void* pData)
{
if (entryProc == NULL) {
return NULL;
}
dr_thread_win32* pThreadWin32 = (dr_thread_win32*)malloc(sizeof(*pThreadWin32));
if (pThreadWin32 != NULL)
{
pThreadWin32->entryProc = entryProc;
pThreadWin32->pData = pData;
pThreadWin32->isInEntryProc = DR_FALSE;
pThreadWin32->hThread = CreateThread(NULL, 0, dr_thread_entry_proc_win32, pThreadWin32, 0, NULL);
if (pThreadWin32 == NULL) {
free(pThreadWin32);
return NULL;
}
// Wait for the new thread to enter into it's entry point before returning. We need to do this so we can safely
// support something like dr_delete_thread(dr_create_thread(my_thread_proc, pData)).
//
// On Win32 there are times when this can get stuck in an infinite loop - I expect it's something to do with some
// bad scheduling by the OS. This can be "fixed" by sleeping for a bit.
while (!pThreadWin32->isInEntryProc) { dr_sleep(0); }
}
return (dr_thread)pThreadWin32;
}
void dr_delete_thread(dr_thread thread)
{
dr_thread_win32* pThreadWin32 = (dr_thread_win32*)thread;
if (pThreadWin32 != NULL)
{
CloseHandle(pThreadWin32->hThread);
}
free(pThreadWin32);
}
void dr_wait_thread(dr_thread thread)
{
dr_thread_win32* pThreadWin32 = (dr_thread_win32*)thread;
if (pThreadWin32 != NULL)
{
WaitForSingleObject(pThreadWin32->hThread, INFINITE);
}
}
void dr_wait_and_delete_thread(dr_thread thread)
{
dr_wait_thread(thread);
dr_delete_thread(thread);
}
#ifdef DR_UTIL_WIN32_USE_CRITICAL_SECTION_MUTEX
dr_mutex dr_create_mutex()
{
dr_mutex mutex = malloc(sizeof(CRITICAL_SECTION));
if (mutex != NULL)
{
InitializeCriticalSection(mutex);
}
return mutex;
}
void dr_delete_mutex(dr_mutex mutex)
{
DeleteCriticalSection(mutex);
free(mutex);
}
void dr_lock_mutex(dr_mutex mutex)
{
EnterCriticalSection(mutex);
}
void dr_unlock_mutex(dr_mutex mutex)
{
LeaveCriticalSection(mutex);
}
#else
dr_mutex dr_create_mutex()
{
return CreateEventA(NULL, FALSE, TRUE, NULL);
}
void dr_delete_mutex(dr_mutex mutex)
{
CloseHandle((HANDLE)mutex);
}
void dr_lock_mutex(dr_mutex mutex)
{
WaitForSingleObject((HANDLE)mutex, INFINITE);
}
void dr_unlock_mutex(dr_mutex mutex)
{
SetEvent((HANDLE)mutex);
}
#endif
dr_semaphore dr_create_semaphore(int initialValue)
{
return (void*)CreateSemaphoreA(NULL, initialValue, LONG_MAX, NULL);
}
void dr_delete_semaphore(dr_semaphore semaphore)
{
CloseHandle(semaphore);
}
dr_bool32 dr_wait_semaphore(dr_semaphore semaphore)
{
return WaitForSingleObject((HANDLE)semaphore, INFINITE) == WAIT_OBJECT_0;
}
dr_bool32 dr_release_semaphore(dr_semaphore semaphore)
{
return ReleaseSemaphore((HANDLE)semaphore, 1, NULL) != 0;
}
#else
void dr_sleep(unsigned int milliseconds)
{
usleep(milliseconds * 1000); // <-- usleep is in microseconds.
}
void dr_yield()
{
sched_yield();
}
unsigned int dr_get_logical_processor_count()
{
return (unsigned int)sysconf(_SC_NPROCESSORS_ONLN);
}
typedef struct
{
/// The Win32 thread handle.
pthread_t pthread;
/// The entry point.
dr_thread_entry_proc entryProc;
/// The user data to pass to the thread's entry point.
void* pData;
/// Set to DR_TRUE by the entry function. We use this to wait for the entry function to start.
dr_bool32 isInEntryProc;
} dr_thread_posix;
static void* dr_thread_entry_proc_posix(void* pDataIn)
{
dr_thread_posix* pThreadPosix = (dr_thread_posix*)pDataIn;
assert(pThreadPosix != NULL);
void* pEntryProcData = pThreadPosix->pData;
dr_thread_entry_proc entryProc = pThreadPosix->entryProc;
assert(entryProc != NULL);
pThreadPosix->isInEntryProc = DR_TRUE;
return (void*)(size_t)entryProc(pEntryProcData);
}
dr_thread dr_create_thread(dr_thread_entry_proc entryProc, void* pData)
{
if (entryProc == NULL) {
return NULL;
}
dr_thread_posix* pThreadPosix = (dr_thread_posix*)malloc(sizeof(*pThreadPosix));
if (pThreadPosix != NULL)
{
pThreadPosix->entryProc = entryProc;
pThreadPosix->pData = pData;
pThreadPosix->isInEntryProc = DR_FALSE;
if (pthread_create(&pThreadPosix->pthread, NULL, dr_thread_entry_proc_posix, pThreadPosix) != 0) {
free(pThreadPosix);
return NULL;
}
// Wait for the new thread to enter into it's entry point before returning. We need to do this so we can safely
// support something like dr_delete_thread(dr_create_thread(my_thread_proc, pData)).
while (!pThreadPosix->isInEntryProc) {}
}
return (dr_thread)pThreadPosix;
}
void dr_delete_thread(dr_thread thread)
{
free(thread);
}
void dr_wait_thread(dr_thread thread)
{
dr_thread_posix* pThreadPosix = (dr_thread_posix*)thread;
if (pThreadPosix != NULL)
{
pthread_join(pThreadPosix->pthread, NULL);
}
}
dr_mutex dr_create_mutex()
{
pthread_mutex_t* mutex = (pthread_mutex_t*)malloc(sizeof(*mutex));
if (pthread_mutex_init(mutex, NULL) != 0) {
free(mutex);
mutex = NULL;
}
return mutex;
}
void dr_delete_mutex(dr_mutex mutex)
{
pthread_mutex_destroy((pthread_mutex_t*)mutex);
}
void dr_lock_mutex(dr_mutex mutex)
{
pthread_mutex_lock((pthread_mutex_t*)mutex);
}
void dr_unlock_mutex(dr_mutex mutex)
{
pthread_mutex_unlock((pthread_mutex_t*)mutex);
}
dr_semaphore dr_create_semaphore(int initialValue)
{
sem_t* semaphore = (sem_t*)malloc(sizeof(*semaphore));
if (sem_init(semaphore, 0, (unsigned int)initialValue) == -1) {
free(semaphore);
semaphore = NULL;
}
return semaphore;
}
void dr_delete_semaphore(dr_semaphore semaphore)
{
sem_close((sem_t*)semaphore);
}
dr_bool32 dr_wait_semaphore(dr_semaphore semaphore)
{
return sem_wait((sem_t*)semaphore) != -1;
}
dr_bool32 dr_release_semaphore(dr_semaphore semaphore)
{
return sem_post((sem_t*)semaphore) != -1;
}
#endif
/////////////////////////////////////////////////////////
// Timing
// macOS does not have clock_gettime on OS X < 10.12
#ifdef __MACH__
#include <AvailabilityMacros.h>
#ifndef MAC_OS_X_VERSION_10_12
#define MAC_OS_X_VERSION_10_12 101200
#endif
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12
#include <mach/mach_time.h>
#define CLOCK_REALTIME 0
#define CLOCK_MONOTONIC 0
int clock_gettime(int clk_id, struct timespec* t)
{
mach_timebase_info_data_t timebase;
mach_timebase_info(&timebase);
dr_uint64 time;
time = mach_absolute_time();
double nseconds = ((double)time * (double)timebase.numer) / ((double)timebase.denom);
double seconds = ((double)time * (double)timebase.numer) / ((double)timebase.denom * 1e9);
t->tv_sec = seconds;
t->tv_nsec = nseconds;
return 0;
}
#endif
#endif
#ifdef _WIN32
static LARGE_INTEGER g_DRTimerFrequency = {{0}};
void dr_timer_init(dr_timer* pTimer)
{
if (g_DRTimerFrequency.QuadPart == 0) {
QueryPerformanceFrequency(&g_DRTimerFrequency);
}
LARGE_INTEGER counter;
QueryPerformanceCounter(&counter);
pTimer->counter = (dr_uint64)counter.QuadPart;
}
double dr_timer_tick(dr_timer* pTimer)
{
LARGE_INTEGER counter;
if (!QueryPerformanceCounter(&counter)) {
return 0;
}
dr_uint64 newTimeCounter = counter.QuadPart;
dr_uint64 oldTimeCounter = pTimer->counter;
pTimer->counter = newTimeCounter;
return (newTimeCounter - oldTimeCounter) / (double)g_DRTimerFrequency.QuadPart;
}
#else
void dr_timer_init(dr_timer* pTimer)
{
struct timespec newTime;
clock_gettime(CLOCK_MONOTONIC, &newTime);
pTimer->counter = (newTime.tv_sec * 1000000000LL) + newTime.tv_nsec;
}
double dr_timer_tick(dr_timer* pTimer)
{
struct timespec newTime;
clock_gettime(CLOCK_MONOTONIC, &newTime);
dr_uint64 newTimeCounter = (newTime.tv_sec * 1000000000LL) + newTime.tv_nsec;
dr_uint64 oldTimeCounter = pTimer->counter;
pTimer->counter = newTimeCounter;
return (newTimeCounter - oldTimeCounter) / 1000000000.0;
}
#endif
/////////////////////////////////////////////////////////
// Random
double dr_randd()
{
return (double)rand() / (double)RAND_MAX;
}
float dr_randf()
{
return (float)dr_randd();
}
/////////////////////////////////////////////////////////
// User Accounts and Process Management
size_t dr_get_username(char* usernameOut, size_t usernameOutSize)
{
if (usernameOut != NULL && usernameOutSize > 0) {
usernameOut[0] = '\0';
}
#ifdef _WIN32
DWORD dwSize = (DWORD)usernameOutSize;
if (!GetUserNameA(usernameOut, &dwSize)) {
return 0;
}
return dwSize;
#else
struct passwd *pw = getpwuid(geteuid());
if (pw == NULL) {
return 0;
}
if (usernameOut != NULL) {
strcpy_s(usernameOut, usernameOutSize, pw->pw_name);
}
return strlen(pw->pw_name);
#endif
}
unsigned int dr_get_process_id()
{
#ifdef _WIN32
return GetProcessId(GetCurrentProcess());
#else
return (unsigned int)getpid();
#endif
}
/////////////////////////////////////////////////////////
// Miscellaneous Stuff.
dr_bool32 dr_hex_char_to_uint(char ascii, unsigned int* out)
{
if (ascii >= '0' && ascii <= '9') {
if (out) *out = ascii - '0';
return DR_TRUE;
}
if (ascii >= 'A' && ascii <= 'F') {
if (out) *out = 10 + (ascii - 'A');
return DR_TRUE;
}
if (ascii >= 'a' && ascii <= 'f') {
if (out) *out = 10 + (ascii - 'a');
return DR_TRUE;
}
if (out) *out = 0;
return DR_FALSE;
}
#endif //DR_IMPLEMENTATION
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// dr_path
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
#ifndef dr_path_h
#define dr_path_h
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
// Structure representing a section of a path.
typedef struct
{
size_t offset;
size_t length;
} drpath_segment;
// Structure used for iterating over a path while at the same time providing useful and easy-to-use information about the iteration.
typedef struct drpath_iterator
{
const char* path;
drpath_segment segment;
} drpath_iterator;
/// Compares a section of two strings for equality.
///
/// @param s0Path [in] The first path.
/// @param s0 [in] The segment of the first path to compare.
/// @param s1Path [in] The second path.
/// @param s1 [in] The segment of the second path to compare.
///
/// @return DR_TRUE if the strings are equal; DR_FALSE otherwise.
dr_bool32 drpath_segments_equal(const char* s0Path, const drpath_segment s0, const char* s1Path, const drpath_segment s1);
/// Creates an iterator for iterating over each segment in a path.
///
/// @param path [in] The path whose segments are being iterated.
///
/// @return True if at least one segment is found; DR_FALSE if it's an empty path.
dr_bool32 drpath_first(const char* path, drpath_iterator* i);
/// Creates an iterator beginning at the last segment.
dr_bool32 drpath_last(const char* path, drpath_iterator* i);
/// Goes to the next segment in the path as per the given iterator.
///
/// @param i [in] A pointer to the iterator to increment.
///
/// @return True if the iterator contains a valid value. Use this to determine when to terminate iteration.
dr_bool32 drpath_next(drpath_iterator* i);
/// Goes to the previous segment in the path.
///
/// @param i [in] A pointer to the iterator to decrement.
///
/// @return DR_TRUE if the iterator contains a valid value. Use this to determine when to terminate iteration.
dr_bool32 drpath_prev(drpath_iterator* i);
/// Determines if the given iterator is at the end.
///
/// @param i [in] The iterator to check.
dr_bool32 drpath_at_end(drpath_iterator i);
/// Determines if the given iterator is at the start.
///
/// @param i [in] The iterator to check.
dr_bool32 drpath_at_start(drpath_iterator i);
/// Compares the string values of two iterators for equality.
///
/// @param i0 [in] The first iterator to compare.
/// @param i1 [in] The second iterator to compare.
///
/// @return DR_TRUE if the strings are equal; DR_FALSE otherwise.
dr_bool32 drpath_iterators_equal(const drpath_iterator i0, const drpath_iterator i1);
/// Determines whether or not the given iterator refers to the root segment of a path.
dr_bool32 drpath_is_root_segment(const drpath_iterator i);
/// Determines whether or not the given iterator refers to a Linux style root directory ("/")
dr_bool32 drpath_is_linux_style_root_segment(const drpath_iterator i);
/// Determines whether or not the given iterator refers to a Windows style root directory.
dr_bool32 drpath_is_win32_style_root_segment(const drpath_iterator i);
/// Converts the slashes in the given path to forward slashes.
///
/// @param path [in] The path whose slashes are being converted.
void drpath_to_forward_slashes(char* path);
/// Converts the slashes in the given path to back slashes.
///
/// @param path [in] The path whose slashes are being converted.
void drpath_to_backslashes(char* path);
/// Determines whether or not the given path is a decendant of another.
///
/// @param descendantAbsolutePath [in] The absolute path of the descendant.
/// @param parentAbsolutePath [in] The absolute path of the parent.
///
/// @remarks
/// As an example, "C:/My/Folder" is a descendant of "C:/".
/// @par
/// If either path contains "." or "..", clean it with drpath_clean() before calling this.
dr_bool32 drpath_is_descendant(const char* descendantAbsolutePath, const char* parentAbsolutePath);
/// Determines whether or not the given path is a direct child of another.
///
/// @param childAbsolutePath [in] The absolute of the child.
/// @param parentAbsolutePath [in] The absolute path of the parent.
///
/// @remarks
/// As an example, "C:/My/Folder" is NOT a child of "C:/" - it is a descendant. Alternatively, "C:/My" IS a child of "C:/".
/// @par
/// If either path contains "." or "..", clean it with drpath_clean() before calling this.
dr_bool32 drpath_is_child(const char* childAbsolutePath, const char* parentAbsolutePath);
/// Modifies the given path by transforming it into it's base path.
///
/// Returns <path>, for convenience.
char* drpath_base_path(char* path);
/// Retrieves the base path from the given path, not including the trailing slash.
///
/// @param path [in] The full path.
/// @param baseOut [out] A pointer to the buffer that will receive the base path.
/// @param baseSizeInBytes [in] The size in bytes of the buffer that will receive the base directory.
///
/// @remarks
/// As an example, when "path" is "C:/MyFolder/MyFile", the output will be "C:/MyFolder". Note that there is no trailing slash.
/// @par
/// If "path" is something like "/MyFolder", the return value will be an empty string.
void drpath_copy_base_path(const char* path, char* baseOut, size_t baseSizeInBytes);
/// Finds the file name portion of the path.
///
/// @param path [in] The path to search.
///
/// @return A pointer to the beginning of the string containing the file name. If this is non-null, it will be an offset of "path".
///
/// @remarks
/// A path with a trailing slash will return an empty string.
/// @par
/// The return value is just an offset of "path".
const char* drpath_file_name(const char* path);
/// Copies the file name into the given buffer.
const char* drpath_copy_file_name(const char* path, char* fileNameOut, size_t fileNameSizeInBytes);
/// Finds the file extension of the given file path.
///
/// @param path [in] The path to search.
///
/// @return A pointer to the beginning of the string containing the file's extension.
///
/// @remarks
/// A path with a trailing slash will return an empty string.
/// @par
/// The return value is just an offset of "path".
/// @par
/// On a path such as "filename.ext1.ext2" the returned string will be "ext2".
const char* drpath_extension(const char* path);
/// Checks whether or not the two paths are equal.
///
/// @param path1 [in] The first path.
/// @param path2 [in] The second path.
///
/// @return DR_TRUE if the paths are equal, DR_FALSE otherwise.
///
/// @remarks
/// This is case-sensitive.
/// @par
/// This is more than just a string comparison. Rather, this splits the path and compares each segment. The path "C:/My/Folder" is considered
/// equal to to "C:\\My\\Folder".
dr_bool32 drpath_equal(const char* path1, const char* path2);
/// Checks if the extension of the given path is equal to the given extension.
///
/// @remarks
/// By default this is NOT case-sensitive, however if the standard library is disable, it is case-sensitive.
dr_bool32 drpath_extension_equal(const char* path, const char* extension);
/// Determines whether or not the given path is relative.
///
/// @param path [in] The path to check.
dr_bool32 drpath_is_relative(const char* path);
/// Determines whether or not the given path is absolute.
///
/// @param path [in] The path to check.
dr_bool32 drpath_is_absolute(const char* path);
/// Appends two paths together, ensuring there is not double up on the last slash.
///
/// @param base [in, out] The base path that is being appended to.
/// @param baseBufferSizeInBytes [in] The size of the buffer pointed to by "base", in bytes.
/// @param other [in] The other path segment.
///
/// @remarks
/// This assumes both paths are well formed and "other" is a relative path.
dr_bool32 drpath_append(char* base, size_t baseBufferSizeInBytes, const char* other);
/// Appends an iterator object to the given base path.
dr_bool32 drpath_append_iterator(char* base, size_t baseBufferSizeInBytes, drpath_iterator i);
/// Appends an extension to the given path.
dr_bool32 drpath_append_extension(char* base, size_t baseBufferSizeInBytes, const char* extension);
/// Appends two paths together, and copyies them to a separate buffer.
///
/// @param dst [out] The destination buffer.
/// @param dstSizeInBytes [in] The size of the buffer pointed to by "dst", in bytes.
/// @param base [in] The base directory.
/// @param other [in] The relative path to append to "base".
///
/// @return DR_TRUE if the paths were appended successfully; DR_FALSE otherwise.
///
/// @remarks
/// This assumes both paths are well formed and "other" is a relative path.
dr_bool32 drpath_copy_and_append(char* dst, size_t dstSizeInBytes, const char* base, const char* other);
/// Appends a base path and an iterator together, and copyies them to a separate buffer.
///
/// @param dst [out] The destination buffer.
/// @param dstSizeInBytes [in] The size of the buffer pointed to by "dst", in bytes.
/// @param base [in] The base directory.
/// @param i [in] The iterator to append.
///
/// @return DR_TRUE if the paths were appended successfully; DR_FALSE otherwise.
///
/// @remarks
/// This assumes both paths are well formed and "i" is a valid iterator.
dr_bool32 drpath_copy_and_append_iterator(char* dst, size_t dstSizeInBytes, const char* base, drpath_iterator i);
/// Appends an extension to the given base path and copies them to a separate buffer.
/// @param dst [out] The destination buffer.
/// @param dstSizeInBytes [in] The size of the buffer pointed to by "dst", in bytes.
/// @param base [in] The base directory.
/// @param extension [in] The relative path to append to "base".
///
/// @return DR_TRUE if the paths were appended successfully; DR_FALSE otherwise.
dr_bool32 drpath_copy_and_append_extension(char* dst, size_t dstSizeInBytes, const char* base, const char* extension);
/// Cleans the path and resolves the ".." and "." segments.
///
/// @param path [in] The path to clean.
/// @param pathOut [out] A pointer to the buffer that will receive the path.
/// @param pathOutSizeInBytes [in] The size of the buffer pointed to by pathOut, in bytes.
///
/// @return The number of bytes written to the output buffer, including the null terminator.
///
/// @remarks
/// The output path will never be longer than the input path.
/// @par
/// The output buffer should never overlap with the input path.
/// @par
/// As an example, the path "my/messy/../path" will result in "my/path"
/// @par
/// The path "my/messy/../../../path" (note how there are too many ".." segments) will return "path" (the extra ".." segments will be dropped.)
/// @par
/// If an error occurs, such as an invalid input path, 0 will be returned.
size_t drpath_clean(const char* path, char* pathOut, size_t pathOutSizeInBytes);
/// Appends one path to the other and then cleans it.
size_t drpath_append_and_clean(char* dst, size_t dstSizeInBytes, const char* base, const char* other);
/// Removes the extension from the given path.
///
/// @remarks
/// If the given path does not have an extension, 1 will be returned, but the string will be left unmodified.
dr_bool32 drpath_remove_extension(char* path);
/// Creates a copy of the given string and removes the extension.
dr_bool32 drpath_copy_and_remove_extension(char* dst, size_t dstSizeInBytes, const char* path);
/// Removes the last segment from the given path.
dr_bool32 drpath_remove_file_name(char* path);
/// Creates a copy of the given string and removes the extension.
dr_bool32 drpath_copy_and_remove_file_name(char* dst, size_t dstSizeInBytes, const char* path);
/// Converts an absolute path to a relative path.
///
/// @return DR_TRUE if the conversion was successful; DR_FALSE if there was an error.
///
/// @remarks
/// This will normalize every slash to forward slashes.
dr_bool32 drpath_to_relative(const char* absolutePathToMakeRelative, const char* absolutePathToMakeRelativeTo, char* relativePathOut, size_t relativePathOutSizeInBytes);
/// Converts a relative path to an absolute path based on a base path.
///
/// @return DR_TRUE if the conversion was successful; DR_FALSE if there was an error.
///
/// @remarks
/// This is equivalent to an append followed by a clean. Slashes will be normalized to forward slashes.
dr_bool32 drpath_to_absolute(const char* relativePathToMakeAbsolute, const char* basePath, char* absolutePathOut, size_t absolutePathOutSizeInBytes);
#ifdef __cplusplus
}
#endif
#endif //dr_path_h
///////////////////////////////////////////////////////////////////////////////
//
// IMPLEMENTATION
//
///////////////////////////////////////////////////////////////////////////////
#ifdef DR_IMPLEMENTATION
#include <string.h>
#include <ctype.h>
#include <errno.h>
dr_bool32 drpath_first(const char* path, drpath_iterator* i)
{
if (i == 0) return DR_FALSE;
i->path = path;
i->segment.offset = 0;
i->segment.length = 0;
if (path == 0 || path[0] == '\0') {
return DR_FALSE;
}
while (i->path[i->segment.length] != '\0' && (i->path[i->segment.length] != '/' && i->path[i->segment.length] != '\\')) {
i->segment.length += 1;
}
return DR_TRUE;
}
dr_bool32 drpath_last(const char* path, drpath_iterator* i)
{
if (i == 0) return DR_FALSE;
i->path = path;
i->segment.offset = 0;
i->segment.length = 0;
if (path == 0 || path[0] == '\0') {
return DR_FALSE;
}
i->path = path;
i->segment.offset = strlen(path);
i->segment.length = 0;
return drpath_prev(i);
}
dr_bool32 drpath_next(drpath_iterator* i)
{
if (i == NULL || i->path == NULL) {
return DR_FALSE;
}
i->segment.offset = i->segment.offset + i->segment.length;
i->segment.length = 0;
while (i->path[i->segment.offset] != '\0' && (i->path[i->segment.offset] == '/' || i->path[i->segment.offset] == '\\')) {
i->segment.offset += 1;
}
if (i->path[i->segment.offset] == '\0') {
return DR_FALSE;
}
while (i->path[i->segment.offset + i->segment.length] != '\0' && (i->path[i->segment.offset + i->segment.length] != '/' && i->path[i->segment.offset + i->segment.length] != '\\')) {
i->segment.length += 1;
}
return DR_TRUE;
}
dr_bool32 drpath_prev(drpath_iterator* i)
{
if (i == NULL || i->path == NULL || i->segment.offset == 0) {
return DR_FALSE;
}
i->segment.length = 0;
do
{
i->segment.offset -= 1;
} while (i->segment.offset > 0 && (i->path[i->segment.offset] == '/' || i->path[i->segment.offset] == '\\'));
if (i->segment.offset == 0) {
if (i->path[i->segment.offset] == '/' || i->path[i->segment.offset] == '\\') {
i->segment.length = 0;
return DR_TRUE;
}
return DR_FALSE;
}
size_t offsetEnd = i->segment.offset + 1;
while (i->segment.offset > 0 && (i->path[i->segment.offset] != '/' && i->path[i->segment.offset] != '\\')) {
i->segment.offset -= 1;
}
if (i->path[i->segment.offset] == '/' || i->path[i->segment.offset] == '\\') {
i->segment.offset += 1;
}
i->segment.length = offsetEnd - i->segment.offset;
return DR_TRUE;
}
dr_bool32 drpath_at_end(drpath_iterator i)
{
return i.path == 0 || i.path[i.segment.offset] == '\0';
}
dr_bool32 drpath_at_start(drpath_iterator i)
{
return i.path != 0 && i.segment.offset == 0;
}
dr_bool32 drpath_iterators_equal(const drpath_iterator i0, const drpath_iterator i1)
{
return drpath_segments_equal(i0.path, i0.segment, i1.path, i1.segment);
}
dr_bool32 drpath_segments_equal(const char* s0Path, const drpath_segment s0, const char* s1Path, const drpath_segment s1)
{
if (s0Path == NULL || s1Path == NULL) {
return DR_FALSE;
}
if (s0.length != s1.length) {
return DR_FALSE;
}
return strncmp(s0Path + s0.offset, s1Path + s1.offset, s0.length) == 0;
}
dr_bool32 drpath_is_root_segment(const drpath_iterator i)
{
return drpath_is_linux_style_root_segment(i) || drpath_is_win32_style_root_segment(i);
}
dr_bool32 drpath_is_linux_style_root_segment(const drpath_iterator i)
{
if (i.path == NULL) {
return DR_FALSE;
}
if (i.segment.offset == 0 && i.segment.length == 0) {
return DR_TRUE; // "/" style root.
}
return DR_FALSE;
}
dr_bool32 drpath_is_win32_style_root_segment(const drpath_iterator i)
{
if (i.path == NULL) {
return DR_FALSE;
}
if (i.segment.offset == 0 && i.segment.length == 2) {
if (((i.path[0] >= 'a' && i.path[0] <= 'z') || (i.path[0] >= 'A' && i.path[0] <= 'Z')) && i.path[1] == ':') {
return DR_TRUE;
}
}
return DR_FALSE;
}
void drpath_to_forward_slashes(char* path)
{
if (path == NULL) {
return;
}
while (path[0] != '\0') {
if (path[0] == '\\') {
path[0] = '/';
}
path += 1;
}
}
void drpath_to_backslashes(char* path)
{
if (path == NULL) {
return;
}
while (path[0] != '\0') {
if (path[0] == '/') {
path[0] = '\\';
}
path += 1;
}
}
dr_bool32 drpath_is_descendant(const char* descendantAbsolutePath, const char* parentAbsolutePath)
{
drpath_iterator iChild;
if (!drpath_first(descendantAbsolutePath, &iChild)) {
return DR_FALSE; // The descendant is an empty string which makes it impossible for it to be a descendant.
}
drpath_iterator iParent;
if (drpath_first(parentAbsolutePath, &iParent))
{
do
{
// If the segment is different, the paths are different and thus it is not a descendant.
if (!drpath_iterators_equal(iParent, iChild)) {
return DR_FALSE;
}
if (!drpath_next(&iChild)) {
return DR_FALSE; // The descendant is shorter which means it's impossible for it to be a descendant.
}
} while (drpath_next(&iParent));
}
return DR_TRUE;
}
dr_bool32 drpath_is_child(const char* childAbsolutePath, const char* parentAbsolutePath)
{
drpath_iterator iChild;
if (!drpath_first(childAbsolutePath, &iChild)) {
return DR_FALSE; // The descendant is an empty string which makes it impossible for it to be a descendant.
}
drpath_iterator iParent;
if (drpath_first(parentAbsolutePath, &iParent))
{
do
{
// If the segment is different, the paths are different and thus it is not a descendant.
if (!drpath_iterators_equal(iParent, iChild)) {
return DR_FALSE;
}
if (!drpath_next(&iChild)) {
return DR_FALSE; // The descendant is shorter which means it's impossible for it to be a descendant.
}
} while (drpath_next(&iParent));
}
// At this point we have finished iteration of the parent, which should be shorter one. We now do one more iterations of
// the child to ensure it is indeed a direct child and not a deeper descendant.
return !drpath_next(&iChild);
}
char* drpath_base_path(char* path)
{
if (path == NULL) {
return NULL;
}
char* pathorig = path;
char* baseend = path;
// We just loop through the path until we find the last slash.
while (path[0] != '\0') {
if (path[0] == '/' || path[0] == '\\') {
baseend = path;
}
path += 1;
}
// Now we just loop backwards until we hit the first non-slash.
while (baseend > path) {
if (baseend[0] != '/' && baseend[0] != '\\') {
break;
}
baseend -= 1;
}
// We just put a null terminator on the end.
baseend[0] = '\0';
return pathorig;
}
void drpath_copy_base_path(const char* path, char* baseOut, size_t baseSizeInBytes)
{
if (path == NULL || baseOut == NULL || baseSizeInBytes == 0) {
return;
}
strcpy_s(baseOut, baseSizeInBytes, path);
drpath_base_path(baseOut);
}
const char* drpath_file_name(const char* path)
{
if (path == NULL) {
return NULL;
}
const char* fileName = path;
// We just loop through the path until we find the last slash.
while (path[0] != '\0') {
if (path[0] == '/' || path[0] == '\\') {
fileName = path;
}
path += 1;
}
// At this point the file name is sitting on a slash, so just move forward.
while (fileName[0] != '\0' && (fileName[0] == '/' || fileName[0] == '\\')) {
fileName += 1;
}
return fileName;
}
const char* drpath_copy_file_name(const char* path, char* fileNameOut, size_t fileNameSizeInBytes)
{
const char* fileName = drpath_file_name(path);
if (fileName != NULL) {
strcpy_s(fileNameOut, fileNameSizeInBytes, fileName);
}
return fileName;
}
const char* drpath_extension(const char* path)
{
if (path == NULL) {
return NULL;
}
const char* extension = drpath_file_name(path);
const char* lastoccurance = 0;
// Just find the last '.' and return.
while (extension[0] != '\0')
{
if (extension[0] == '.') {
extension += 1;
lastoccurance = extension;
}
extension += 1;
}
return (lastoccurance != 0) ? lastoccurance : extension;
}
dr_bool32 drpath_equal(const char* path1, const char* path2)
{
if (path1 == NULL || path2 == NULL) {
return DR_FALSE;
}
if (path1 == path2 || (path1[0] == '\0' && path2[0] == '\0')) {
return DR_TRUE; // Two empty paths are treated as the same.
}
drpath_iterator iPath1;
drpath_iterator iPath2;
if (drpath_first(path1, &iPath1) && drpath_first(path2, &iPath2))
{
dr_bool32 isPath1Valid;
dr_bool32 isPath2Valid;
do
{
if (!drpath_iterators_equal(iPath1, iPath2)) {
return DR_FALSE;
}
isPath1Valid = drpath_next(&iPath1);
isPath2Valid = drpath_next(&iPath2);
} while (isPath1Valid && isPath2Valid);
// At this point either iPath1 and/or iPath2 have finished iterating. If both of them are at the end, the two paths are equal.
return isPath1Valid == isPath2Valid && iPath1.path[iPath1.segment.offset] == '\0' && iPath2.path[iPath2.segment.offset] == '\0';
}
return DR_FALSE;
}
dr_bool32 drpath_extension_equal(const char* path, const char* extension)
{
if (path == NULL || extension == NULL) {
return DR_FALSE;
}
const char* ext1 = extension;
const char* ext2 = drpath_extension(path);
#ifdef _MSC_VER
return _stricmp(ext1, ext2) == 0;
#else
return strcasecmp(ext1, ext2) == 0;
#endif
}
dr_bool32 drpath_is_relative(const char* path)
{
if (path == NULL) {
return DR_FALSE;
}
drpath_iterator seg;
if (drpath_first(path, &seg)) {
return !drpath_is_root_segment(seg);
}
// We'll get here if the path is empty. We consider this to be a relative path.
return DR_TRUE;
}
dr_bool32 drpath_is_absolute(const char* path)
{
return !drpath_is_relative(path);
}
dr_bool32 drpath_append(char* base, size_t baseBufferSizeInBytes, const char* other)
{
if (base == NULL || other == NULL) {
return DR_FALSE;
}
size_t path1Length = strlen(base);
size_t path2Length = strlen(other);
if (path1Length >= baseBufferSizeInBytes) {
return DR_FALSE;
}
// Slash.
if (path1Length > 0 && base[path1Length - 1] != '/' && base[path1Length - 1] != '\\') {
base[path1Length] = '/';
path1Length += 1;
}
// Other part.
if (path1Length + path2Length >= baseBufferSizeInBytes) {
path2Length = baseBufferSizeInBytes - path1Length - 1; // -1 for the null terminator.
}
strncpy_s(base + path1Length, baseBufferSizeInBytes - path1Length, other, path2Length);
return DR_TRUE;
}
dr_bool32 drpath_append_iterator(char* base, size_t baseBufferSizeInBytes, drpath_iterator i)
{
if (base == NULL) {
return DR_FALSE;
}
size_t path1Length = strlen(base);
size_t path2Length = i.segment.length;
if (path1Length >= baseBufferSizeInBytes) {
return DR_FALSE;
}
if (drpath_is_linux_style_root_segment(i)) {
if (baseBufferSizeInBytes > 1) {
base[0] = '/';
base[1] = '\0';
return DR_TRUE;
}
}
// Slash.
if (path1Length > 0 && base[path1Length - 1] != '/' && base[path1Length - 1] != '\\') {
base[path1Length] = '/';
path1Length += 1;
}
// Other part.
if (path1Length + path2Length >= baseBufferSizeInBytes) {
path2Length = baseBufferSizeInBytes - path1Length - 1; // -1 for the null terminator.
}
strncpy_s(base + path1Length, baseBufferSizeInBytes - path1Length, i.path + i.segment.offset, path2Length);
return DR_TRUE;
}
dr_bool32 drpath_append_extension(char* base, size_t baseBufferSizeInBytes, const char* extension)
{
if (base == NULL || extension == NULL) {
return DR_FALSE;
}
size_t baseLength = strlen(base);
size_t extLength = strlen(extension);
if (baseLength >= baseBufferSizeInBytes) {
return DR_FALSE;
}
base[baseLength] = '.';
baseLength += 1;
if (baseLength + extLength >= baseBufferSizeInBytes) {
extLength = baseBufferSizeInBytes - baseLength - 1; // -1 for the null terminator.
}
strncpy_s(base + baseLength, baseBufferSizeInBytes - baseLength, extension, extLength);
return DR_TRUE;
}
dr_bool32 drpath_copy_and_append(char* dst, size_t dstSizeInBytes, const char* base, const char* other)
{
if (dst == NULL || dstSizeInBytes == 0) {
return DR_FALSE;
}
strcpy_s(dst, dstSizeInBytes, base);
return drpath_append(dst, dstSizeInBytes, other);
}
dr_bool32 drpath_copy_and_append_iterator(char* dst, size_t dstSizeInBytes, const char* base, drpath_iterator i)
{
if (dst == NULL || dstSizeInBytes == 0) {
return DR_FALSE;
}
strcpy_s(dst, dstSizeInBytes, base);
return drpath_append_iterator(dst, dstSizeInBytes, i);
}
dr_bool32 drpath_copy_and_append_extension(char* dst, size_t dstSizeInBytes, const char* base, const char* extension)
{
if (dst == NULL || dstSizeInBytes == 0) {
return DR_FALSE;
}
strcpy_s(dst, dstSizeInBytes, base);
return drpath_append_extension(dst, dstSizeInBytes, extension);
}
// This function recursively cleans a path which is defined as a chain of iterators. This function works backwards, which means at the time of calling this
// function, each iterator in the chain should be placed at the end of the path.
//
// This does not write the null terminator, nor a leading slash for absolute paths.
size_t _drpath_clean_trywrite(drpath_iterator* iterators, unsigned int iteratorCount, char* pathOut, size_t pathOutSizeInBytes, unsigned int ignoreCounter)
{
if (iteratorCount == 0) {
return 0;
}
drpath_iterator isegment = iterators[iteratorCount - 1];
// If this segment is a ".", we ignore it. If it is a ".." we ignore it and increment "ignoreCount".
int ignoreThisSegment = ignoreCounter > 0 && isegment.segment.length > 0;
if (isegment.segment.length == 1 && isegment.path[isegment.segment.offset] == '.')
{
// "."
ignoreThisSegment = 1;
}
else
{
if (isegment.segment.length == 2 && isegment.path[isegment.segment.offset] == '.' && isegment.path[isegment.segment.offset + 1] == '.')
{
// ".."
ignoreThisSegment = 1;
ignoreCounter += 1;
}
else
{
// It's a regular segment, so decrement the ignore counter.
if (ignoreCounter > 0) {
ignoreCounter -= 1;
}
}
}
// The previous segment needs to be written before we can write this one.
size_t bytesWritten = 0;
drpath_iterator prev = isegment;
if (!drpath_prev(&prev))
{
if (iteratorCount > 1)
{
iteratorCount -= 1;
prev = iterators[iteratorCount - 1];
}
else
{
prev.path = NULL;
prev.segment.offset = 0;
prev.segment.length = 0;
}
}
if (prev.segment.length > 0)
{
iterators[iteratorCount - 1] = prev;
bytesWritten = _drpath_clean_trywrite(iterators, iteratorCount, pathOut, pathOutSizeInBytes, ignoreCounter);
}
if (!ignoreThisSegment)
{
if (pathOutSizeInBytes > 0)
{
pathOut += bytesWritten;
pathOutSizeInBytes -= bytesWritten;
if (bytesWritten > 0)
{
pathOut[0] = '/';
bytesWritten += 1;
pathOut += 1;
pathOutSizeInBytes -= 1;
}
if (pathOutSizeInBytes >= isegment.segment.length)
{
strncpy_s(pathOut, pathOutSizeInBytes, isegment.path + isegment.segment.offset, isegment.segment.length);
bytesWritten += isegment.segment.length;
}
else
{
strncpy_s(pathOut, pathOutSizeInBytes, isegment.path + isegment.segment.offset, pathOutSizeInBytes);
bytesWritten += pathOutSizeInBytes;
}
}
}
return bytesWritten;
}
size_t drpath_clean(const char* path, char* pathOut, size_t pathOutSizeInBytes)
{
if (path == NULL) {
return 0;
}
drpath_iterator last;
if (drpath_last(path, &last))
{
size_t bytesWritten = 0;
if (path[0] == '/') {
if (pathOut != NULL && pathOutSizeInBytes > 1) {
pathOut[0] = '/';
bytesWritten = 1;
}
}
bytesWritten += _drpath_clean_trywrite(&last, 1, pathOut + bytesWritten, pathOutSizeInBytes - bytesWritten - 1, 0); // -1 to ensure there is enough room for a null terminator later on.
if (pathOutSizeInBytes > bytesWritten) {
pathOut[bytesWritten] = '\0';
}
return bytesWritten + 1;
}
return 0;
}
size_t drpath_append_and_clean(char* dst, size_t dstSizeInBytes, const char* base, const char* other)
{
if (base == NULL || other == NULL) {
return 0;
}
drpath_iterator last[2] = {
{NULL, {0, 0}},
{NULL, {0, 0}}
};
dr_bool32 isPathEmpty0 = !drpath_last(base, last + 0);
dr_bool32 isPathEmpty1 = !drpath_last(other, last + 1);
int iteratorCount = !isPathEmpty0 + !isPathEmpty1;
if (iteratorCount == 0) {
return 0; // Both input strings are empty.
}
size_t bytesWritten = 0;
if (base[0] == '/') {
if (dst != NULL && dstSizeInBytes > 1) {
dst[0] = '/';
bytesWritten = 1;
}
}
bytesWritten += _drpath_clean_trywrite(last, 2, dst + bytesWritten, dstSizeInBytes - bytesWritten - 1, 0); // -1 to ensure there is enough room for a null terminator later on.
if (dstSizeInBytes > bytesWritten) {
dst[bytesWritten] = '\0';
}
return bytesWritten + 1;
}
dr_bool32 drpath_remove_extension(char* path)
{
if (path == NULL) {
return DR_FALSE;
}
const char* extension = drpath_extension(path);
if (extension != NULL) {
extension -= 1;
path[(extension - path)] = '\0';
}
return DR_TRUE;
}
dr_bool32 drpath_copy_and_remove_extension(char* dst, size_t dstSizeInBytes, const char* path)
{
if (dst == NULL || dstSizeInBytes == 0 || path == NULL) {
return DR_FALSE;
}
const char* extension = drpath_extension(path);
if (extension != NULL) {
extension -= 1;
}
strncpy_s(dst, dstSizeInBytes, path, (size_t)(extension - path));
return DR_TRUE;
}
dr_bool32 drpath_remove_file_name(char* path)
{
if (path == NULL) {
return DR_FALSE;
}
// We just create an iterator that starts at the last segment. We then move back one and place a null terminator at the end of
// that segment. That will ensure the resulting path is not left with a slash.
drpath_iterator iLast;
if (!drpath_last(path, &iLast)) {
return DR_FALSE; // The path is empty.
}
if (drpath_is_root_segment(iLast)) {
return DR_FALSE;
}
drpath_iterator iSecondLast = iLast;
if (drpath_prev(&iSecondLast))
{
// There is a segment before it so we just place a null terminator at the end, but only if it's not the root of a Linux-style absolute path.
if (drpath_is_linux_style_root_segment(iSecondLast)) {
path[iLast.segment.offset] = '\0';
} else {
path[iSecondLast.segment.offset + iSecondLast.segment.length] = '\0';
}
}
else
{
// This is already the last segment, so just place a null terminator at the beginning of the string.
path[0] = '\0';
}
return DR_TRUE;
}
dr_bool32 drpath_copy_and_remove_file_name(char* dst, size_t dstSizeInBytes, const char* path)
{
if (dst == NULL) {
return DR_FALSE;
}
if (dstSizeInBytes == 0) {
return DR_FALSE;
}
if (path == NULL) {
dst[0] = '\0';
return DR_FALSE;
}
// We just create an iterator that starts at the last segment. We then move back one and place a null terminator at the end of
// that segment. That will ensure the resulting path is not left with a slash.
drpath_iterator iLast;
if (!drpath_last(path, &iLast)) {
dst[0] = '\0';
return DR_FALSE; // The path is empty.
}
if (drpath_is_linux_style_root_segment(iLast)) {
if (dstSizeInBytes > 1) {
dst[0] = '/'; dst[1] = '\0';
return DR_FALSE;
} else {
dst[0] = '\0';
return DR_FALSE;
}
}
if (drpath_is_win32_style_root_segment(iLast)) {
return strncpy_s(dst, dstSizeInBytes, path + iLast.segment.offset, iLast.segment.length) == 0;
}
drpath_iterator iSecondLast = iLast;
if (drpath_prev(&iSecondLast))
{
// There is a segment before it so we just place a null terminator at the end, but only if it's not the root of a Linux-style absolute path.
if (drpath_is_linux_style_root_segment(iSecondLast)) {
return strcpy_s(dst, dstSizeInBytes, "/") == 0;
} else {
return strncpy_s(dst, dstSizeInBytes, path, iSecondLast.segment.offset + iSecondLast.segment.length) == 0;
}
}
else
{
// This is already the last segment, so just place a null terminator at the beginning of the string.
dst[0] = '\0';
return DR_TRUE;
}
}
dr_bool32 drpath_to_relative(const char* absolutePathToMakeRelative, const char* absolutePathToMakeRelativeTo, char* relativePathOut, size_t relativePathOutSizeInBytes)
{
// We do this in two phases. The first phase just iterates past each segment of both the path to convert and the
// base path until we find two that are not equal. The second phase just adds the appropriate ".." segments.
if (relativePathOut == 0) {
return DR_FALSE;
}
if (relativePathOutSizeInBytes == 0) {
return DR_FALSE;
}
if (!drpath_is_absolute(absolutePathToMakeRelative) || !drpath_is_absolute(absolutePathToMakeRelativeTo)) {
return DR_FALSE;
}
drpath_iterator iPath;
drpath_iterator iBase;
dr_bool32 isPathEmpty = !drpath_first(absolutePathToMakeRelative, &iPath);
dr_bool32 isBaseEmpty = !drpath_first(absolutePathToMakeRelativeTo, &iBase);
if (isPathEmpty && isBaseEmpty)
{
// Looks like both paths are empty.
relativePathOut[0] = '\0';
return DR_FALSE;
}
// Phase 1: Get past the common section.
int isPathAtEnd = 0;
int isBaseAtEnd = 0;
while (!isPathAtEnd && !isBaseAtEnd && drpath_iterators_equal(iPath, iBase))
{
isPathAtEnd = !drpath_next(&iPath);
isBaseAtEnd = !drpath_next(&iBase);
}
if (iPath.segment.offset == 0)
{
// The path is not relative to the base path.
relativePathOut[0] = '\0';
return DR_FALSE;
}
// Phase 2: Append ".." segments - one for each remaining segment in the base path.
char* pDst = relativePathOut;
size_t bytesAvailable = relativePathOutSizeInBytes;
if (!drpath_at_end(iBase))
{
do
{
if (pDst != relativePathOut)
{
if (bytesAvailable <= 3)
{
// Ran out of room.
relativePathOut[0] = '\0';
return DR_FALSE;
}
pDst[0] = '/';
pDst[1] = '.';
pDst[2] = '.';
pDst += 3;
bytesAvailable -= 3;
}
else
{
// It's the first segment, so we need to ensure we don't lead with a slash.
if (bytesAvailable <= 2)
{
// Ran out of room.
relativePathOut[0] = '\0';
return DR_FALSE;
}
pDst[0] = '.';
pDst[1] = '.';
pDst += 2;
bytesAvailable -= 2;
}
} while (drpath_next(&iBase));
}
// Now we just append whatever is left of the main path. We want the path to be clean, so we append segment-by-segment.
if (!drpath_at_end(iPath))
{
do
{
// Leading slash, if required.
if (pDst != relativePathOut)
{
if (bytesAvailable <= 1)
{
// Ran out of room.
relativePathOut[0] = '\0';
return DR_FALSE;
}
pDst[0] = '/';
pDst += 1;
bytesAvailable -= 1;
}
if (bytesAvailable <= iPath.segment.length)
{
// Ran out of room.
relativePathOut[0] = '\0';
return DR_FALSE;
}
if (strncpy_s(pDst, bytesAvailable, iPath.path + iPath.segment.offset, iPath.segment.length) != 0)
{
// Error copying the string. Probably ran out of room in the output buffer.
relativePathOut[0] = '\0';
return DR_FALSE;
}
pDst += iPath.segment.length;
bytesAvailable -= iPath.segment.length;
} while (drpath_next(&iPath));
}
// Always null terminate.
//assert(bytesAvailable > 0);
pDst[0] = '\0';
return DR_TRUE;
}
dr_bool32 drpath_to_absolute(const char* relativePathToMakeAbsolute, const char* basePath, char* absolutePathOut, size_t absolutePathOutSizeInBytes)
{
return drpath_append_and_clean(absolutePathOut, absolutePathOutSizeInBytes, basePath, relativePathToMakeAbsolute) != 0;
}
#endif //DR_IMPLEMENTATION
/*
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
*/