Last active
January 8, 2023 15:10
-
-
Save incrediblejr/2b2219334b0b2a5ba18f5026fd8ebca2 to your computer and use it in GitHub Desktop.
join multiple path segments together with one allocation from optional user provided custom allocator or default (malloc)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef IJPATHJOIN_INCLUDED_H | |
#define IJPATHJOIN_INCLUDED_H | |
#include <stdarg.h> /* va_list */ | |
#ifdef __cplusplus | |
extern "C" { | |
#endif | |
#if defined(IJPATHJOIN_STATIC) | |
#define IJPATHJOIN_API static | |
#else | |
#define IJPATHJOIN_API extern | |
#endif | |
enum ijpathjoin_flags { | |
IJPJ_NO_FLAGS = 0, | |
IJPJ_PATH_SEPARATOR_FORWARD = 1 << 0, | |
IJPJ_PATH_SEPARATOR_BACK = 1 << 1, | |
IJPJ_PATH_SEPARATOR_BACK_ESCAPE = 1 << 2, | |
IJPJ_QUOTE_PATH = 1 << 3, | |
IJPJ_DISABLE_ROOT_CLASSIFICATION = 1 << 4 | |
}; | |
/* join multiple path segments together with one allocation from optional user | |
provided custom allocator or default (malloc), unless overridden by `IJPATHJOIN_malloc` | |
ex: | |
ijpathjoin("folder_a", "/folder_b/folder_c", "folder_d") == "folder_a/folder_b/folder_c/folder_d" | |
ijpathjoin("/a", "b", "c") == "/a/b/c" | |
ijpathjoin("/a", "\b\", "c") == "/a/b/c" | |
ijpathjoin("a", "\\b\\", "/c") == "a/b/c" | |
resulting path is tidy, i.e. always same path separator and no "running" | |
separators (//folder_a///folder_b) (unless `IJPJ_PATH_SEPARATOR_BACK_ESCAPE` | |
was requested) | |
use `IJPJ_PATH_SEPARATOR_` flags to control the separators in the result. | |
NB: defaults to forward (/) path separator unless overridden by flags. | |
use `IJPJ_QUOTE_PATH` to have the result be quoted. | |
if the first parameter starts with a path separator then the resulting path | |
is also drive relative i.e. the path separator is preserved. ex | |
ijpathjoin("/a", "b", "c") == "/a/b/c" | |
use flag `IJPJ_DISABLE_ROOT_CLASSIFICATION` to disable this behaviour, resulting | |
in no leading path separator ("a/b/c" for the above example) | |
NB: if not using provided macro(s) take care to properly terminate the variadic arguments | |
properly, i.e. not with a 0 (zero) but rather with a _null pointer_ | |
(optional) allocation function: | |
void *allocfunc(void *userdata, unsigned numbytes_to_allocate) | |
*/ | |
IJPATHJOIN_API char *ijpathjoinv(void *(*allocfunc)(void*, unsigned), void *ud, unsigned flags, const char *s, va_list args); | |
IJPATHJOIN_API char *ijpathjoinva(void *(*allocfunc)(void*, unsigned), void *ud, unsigned flags, const char *s, ...); | |
#define ijpathjoinex(af, ud, flags, s, ...) ijpathjoinva(af, (void*)ud, flags, s, ##__VA_ARGS__, (const char*)0) | |
#define ijpathjoin(s, ...) ijpathjoinex(0, 0, IJPJ_NO_FLAGS, s, ##__VA_ARGS__, (const char*)0) | |
#ifdef __cplusplus | |
} | |
#endif | |
#endif | |
#if defined(IJPATHJOIN_IMPLEMENTATION) | |
#ifndef IJPATHJOIN_malloc | |
#include <stdlib.h> | |
#define IJPATHJOIN_malloc malloc | |
#endif | |
#ifndef va_copy | |
#ifdef __va_copy | |
#define va_copy(a,b) __va_copy(a,b) | |
#else | |
#define va_copy(a,b) ((a)=(b)) | |
#endif | |
#endif | |
static int ijpathjoin__is_path_separator(int c) { return c == '\\' || c == '/'; } | |
#define IJPATHJOIN__RELATIVE_CURRENT_DRIVE (1) | |
static int ijpathjoin__classify_root(const char *s) { | |
if (ijpathjoin__is_path_separator(*s)) | |
return IJPATHJOIN__RELATIVE_CURRENT_DRIVE; | |
return 0; | |
} | |
static unsigned ijpathjoin__classification_size(int classification, unsigned path_separator_substitute_len) { | |
if (classification == IJPATHJOIN__RELATIVE_CURRENT_DRIVE) | |
return path_separator_substitute_len; | |
return 0; | |
} | |
static char *ijpathjoin__classification_copy(char *s, int classification, char path_separator_substitute, unsigned path_separator_substitute_len) { | |
if (classification == IJPATHJOIN__RELATIVE_CURRENT_DRIVE) { | |
while (path_separator_substitute_len--) | |
*s++ = path_separator_substitute; | |
} | |
return s; | |
} | |
static unsigned ijpathjoin__copy_len(const char *s, unsigned path_separator_substitute_len) { | |
char c; | |
unsigned len = 0; | |
while (ijpathjoin__is_path_separator(*s)) | |
++s; | |
while ((c = *s++) != '\0') { | |
unsigned count = 1; | |
switch (c) { | |
case '/': | |
case '\\': { | |
count = path_separator_substitute_len; | |
/* next is path separator ? */ | |
if (ijpathjoin__is_path_separator(*s)) | |
continue; | |
} | |
/* FALLTHRU */ | |
default: | |
len += count; | |
} | |
} | |
if (len) | |
len -= (ijpathjoin__is_path_separator(s[-2])*path_separator_substitute_len); | |
return len; | |
} | |
static char *ijpathjoin__copy(char *dst, const char *src, unsigned copylen, char path_separator_substitute, unsigned path_separator_substitute_len) { | |
char c; | |
while (ijpathjoin__is_path_separator(*src)) | |
++src; | |
while (copylen && ((c = *src++) != '\0')) { | |
unsigned len = 1; | |
switch (c) { | |
case '/': | |
case '\\': { | |
len = path_separator_substitute_len; | |
c = path_separator_substitute; | |
/* next is path separator ? */ | |
if (ijpathjoin__is_path_separator(*src)) | |
continue; | |
} | |
/* FALLTHRU */ | |
default: | |
while (len--) { | |
*dst++ = c; | |
--copylen; | |
} | |
} | |
} | |
*dst = '\0'; | |
return dst; | |
} | |
IJPATHJOIN_API char *ijpathjoinv(void *(*allocfunc)(void*, unsigned), void *ud, unsigned flags, const char *s, va_list args) { | |
unsigned reslen = 0; | |
unsigned num_pathsegments = 0; | |
char *res = 0; | |
char *p = 0; | |
const char *part; | |
unsigned path_separator_substitute_len; | |
char path_separator_substitute; | |
int root_classification; | |
va_list ap; | |
if (!s) | |
return 0; | |
if (flags&IJPJ_DISABLE_ROOT_CLASSIFICATION) | |
root_classification = 0; | |
else | |
root_classification = ijpathjoin__classify_root(s); | |
if (flags&(IJPJ_PATH_SEPARATOR_BACK|IJPJ_PATH_SEPARATOR_BACK_ESCAPE)) { | |
path_separator_substitute = '\\'; | |
path_separator_substitute_len = 1 + ((flags&IJPJ_PATH_SEPARATOR_BACK_ESCAPE) == IJPJ_PATH_SEPARATOR_BACK_ESCAPE); | |
} else { | |
path_separator_substitute = '/'; | |
path_separator_substitute_len = 1; | |
} | |
loop: | |
va_copy(ap, args); | |
while ((part = va_arg(ap, const char *)) != 0) { | |
unsigned len; | |
len = ijpathjoin__copy_len(part, path_separator_substitute_len); | |
if (!res) { | |
++num_pathsegments; | |
reslen += len; | |
} else { | |
unsigned left = path_separator_substitute_len; | |
while (left--) | |
*p++ = path_separator_substitute; | |
ijpathjoin__copy(p, part, len, path_separator_substitute, path_separator_substitute_len); | |
p += len; | |
} | |
} | |
va_end(ap); | |
if (!res) { | |
unsigned len = ijpathjoin__copy_len(s, path_separator_substitute_len); | |
if (flags&IJPJ_QUOTE_PATH) | |
reslen += 2; | |
reslen += ijpathjoin__classification_size(root_classification, path_separator_substitute_len); | |
reslen += (num_pathsegments*path_separator_substitute_len)+len+1; | |
res = p = (char*)(allocfunc ? (*allocfunc)(ud, reslen) : IJPATHJOIN_malloc(reslen)); | |
if (!res) | |
return 0; | |
if (flags&IJPJ_QUOTE_PATH) | |
*p++ = '"'; | |
p = ijpathjoin__classification_copy(p, root_classification, path_separator_substitute, path_separator_substitute_len); | |
ijpathjoin__copy(p, s, len, path_separator_substitute, path_separator_substitute_len); | |
p += len; | |
if (num_pathsegments) | |
goto loop; | |
} | |
if (flags&IJPJ_QUOTE_PATH) | |
*p++ = '"'; | |
*p = '\0'; | |
return res; | |
} | |
IJPATHJOIN_API char *ijpathjoinva(void *(*allocfunc)(void*, unsigned), void *ud, unsigned flags, const char *s, ...) { | |
char *res; | |
va_list ap; | |
va_start(ap, s); | |
res = ijpathjoinv(allocfunc, ud, flags, s, ap); | |
va_end(ap); | |
return res; | |
} | |
#undef IJPATHJOIN__RELATIVE_CURRENT_DRIVE | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment