Skip to content

Instantly share code, notes, and snippets.

@incrediblejr
Last active January 8, 2023 15:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save incrediblejr/2b2219334b0b2a5ba18f5026fd8ebca2 to your computer and use it in GitHub Desktop.
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)
#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