Codync
무료 오픈소스 1:1 대안, Grok Bot과 Muse 대신
snakepath는 헤더 전용 C99 구현으로 Python pathlib 기능을 메모리 할당 없이 POSIX·Windows에서 제공합니다
Header-only C99 port of Python's pathlib: join, split, glob, walk, resolve and stat paths on POSIX and Windows with no malloc. Passes CPython's own pathlib test suite.
개발 도구GitHub @netanel-haber온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
저장소 정보는 아직 수집하지 않았습니다.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
Python's Pathlib in C99. Single header, STB style. With Claude Opus 4.5+6/5.5 and codex 5.2/3/4/5. Use at your own peril.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗C99 STB-style header-only port of Python's pathlib. Passes CPython 3.15's own pathlib test suite.
POSIX + Windows. No malloc (OS functions like opendir/stat may allocate internally).
Vibe-coded with Claude + Codex.
The original API was created by Antoine Pitrou.
u=https://raw.githubusercontent.com/netanel-haber/snakepath/main/snakepath.h &&
curl -sSLo snakepath.h "$u" &&
cat <<'EOF' | cc -xc - -o demo &&
#define SP_PATH_MAX 4096
#define SNAKEPATH_FLUENT
#define SNAKEPATH_IMPLEMENTATION
#include "snakepath.h"
#include <stdio.h>
int main(void) {
SpPath boring = sp_path("/foo/bar.txt");
printf("BORING API: %s\n", sp_name(&boring).buf);
printf("BORING API: %s\n", sp_stem(&boring).buf);
SpPath fluent = SPF("/etc")->join("nginx")->join("nginx.conf")->path();
printf("FLUENT API: %s\n", sp_str(&fluent));
}
EOF
./demo
rm -f demo snakepath.h
cc -x c -o nob nob.h && ./nob
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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 OR COPYRIGHT HOLDERS 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.
/* compile: cc api_demo.c -o api_demo && ./api_demo
* Creates a demo_tmp/ directory in cwd, cleans up on exit. */
#define SP_PATH_MAX 4096
#define SNAKEPATH_FLUENT
#define SNAKEPATH_IMPLEMENTATION
#include "snakepath.h"
#include <stdio.h>
#include <string.h>
static void section(const char *s) { printf("\n--- %s ---\n\n", s); }
/* Actions return an SpError, which the compiler won't let you ignore */
static void check(const char *what, SpError err) { printf(" %-9s %s\n", what, sp_error_str(err)); }
int main(void) {
SpPath tmp;
SpPath p = sp_path("/home/user/docs/report.tar.gz");
SpPath posix = sp_path_f("/etc/nginx/nginx.conf", SP_FLAVOR_POSIX);
SpPath win = sp_path_f("C:\\Users\\dev\\file.txt", SP_FLAVOR_WINDOWS);
SpPath base = sp_path("/home/user");
/* Fluent entry: SPF("path"), SPF_P("path"), SPF_W("path"), SPF_PATH(var) */
/* ── Components ────────────────────────────────────────────── */
section("Components");
/* .drive https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.drive */
printf(" drive: \"%s\"\n", sp_drive(&p).buf);
printf(" (fluent) \"%s\"\n", SPF_PATH(p)->drive().buf);
/* .root https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.root */
printf(" root: %s\n", sp_root(&p).buf);
printf(" (fluent) %s\n", SPF_PATH(p)->root().buf);
/* .anchor https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.anchor */
printf(" anchor: %s\n", sp_anchor(&p).buf);
printf(" (fluent) %s\n", SPF_PATH(p)->anchor().buf);
/* .name https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.name */
printf(" name: %s\n", sp_name(&p).buf);
printf(" (fluent) %s\n", SPF_PATH(p)->name().buf);
/* .stem https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.stem */
printf(" stem: %s\n", sp_stem(&p).buf);
printf(" (fluent) %s\n", SPF_PATH(p)->stem().buf);
/* .suffix https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.suffix */
printf(" suffix: %s\n", sp_suffix(&p).buf);
printf(" (fluent) %s\n", SPF_PATH(p)->suffix().buf);
/* .suffixes https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.suffixes
* Returns ALL dot-separated segments, not just "real" extensions. */
SpSuffixes sfx = sp_suffixes(&p);
printf(" suffixes (%zu):", sfx.count);
for (size_t i = 0; i < sfx.count; i++)
printf(" %.*s", (int)sfx.items[i].len, sfx.items[i].data);
printf("\n");
/* Windows / POSIX flavor differences */
printf(" win drive: %s, root: %s\n", sp_drive(&win).buf, sp_root(&win).buf);
printf(" posix drive: \"%s\", root: %s\n", sp_drive(&posix).buf, sp_root(&posix).buf);
/* ── Navigation ────────────────────────────────────────────── */
section("Navigation");
/* .parent https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parent */
tmp = sp_parent(&p);
printf(" parent: %s\n", sp_str(&tmp));
tmp = SPF_PATH(p)->parent()->path();
printf(" (fluent) %s\n", sp_str(&tmp));
/* .parents https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parents
* Terminates at "/" for absolute, "." for relative. */
SpParentsIter pit = sp_parents_begin(&p);
SpPath par;
printf(" parents:\n");
while (sp_parents_next(&pit, &par))
printf(" %s\n", sp_str(&par));
/* .parts https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parts */
SpPartsIter parts_it = sp_parts_begin(&p);
SpStr part;
printf(" parts:");
while (sp_parts_next(&parts_it, &part))
printf(" [%.*s]", (int)part.len, part.data);
printf("\n");
/* ── Joining ───────────────────────────────────────────────── */
section("Joining");
/* .joinpath https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.joinpath */
tmp = sp_join_one(&base, "docs");
printf(" join_one: %s\n", sp_str(&tmp));
tmp = SPF_PATH(base)->join("docs")->path();
printf(" (fluent) %s\n", sp_str(&tmp));
tmp = sp_join(&base, "a", "b", "c"); /* variadic, C only */
printf(" join: %s\n", sp_str(&tmp));
SpPath other = sp_path("extra");
tmp = sp_join_n(&base, other.buf, other.len);
printf(" joinpath: %s\n", sp_str(&tmp));
/* .with_segments https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.with_segments */
const char *segments[] = {"var", "log", "nginx"};
tmp = sp_with_segments(&base, segments, SP_ARRAY_LEN(segments));
printf(" with_segments: %s\n", sp_str(&tmp));
tmp = SPF_PATH(base)->with_segments(segments, SP_ARRAY_LEN(segments))->path();
printf(" (fluent) %s\n", sp_str(&tmp));
/* ── Modification ──────────────────────────────────────────── */
section("Modification");
/* .with_name https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.with_name */
tmp = sp_with_name(&p, "README.md");
printf(" with_name: %s\n", sp_str(&tmp));
tmp = SPF_PATH(p)->with_name("README.md")->path();
printf(" (fluent) %s\n", sp_str(&tmp));
/* .with_stem https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.with_stem */
tmp = sp_with_stem(&p, "archive");
printf(" with_stem: %s\n", sp_str(&tmp));
tmp = SPF_PATH(p)->with_stem("archive")->path();
printf(" (fluent) %s\n", sp_str(&tmp));
/* .with_suffix https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.with_suffix */
tmp = sp_with_suffix(&p, ".bz2");
printf(" with_suffix: %s\n", sp_str(&tmp));
tmp = SPF_PATH(p)->with_suffix(".bz2")->path();
printf(" (fluent) %s\n", sp_str(&tmp));
/* ── Queries ───────────────────────────────────────────────── */
section("Queries");
/* .is_absolute https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.is_absolute */
printf(" is_absolute: %d\n", sp_is_absolute(&p));
printf(" (fluent) %d\n", SPF_PATH(p)->is_absolute());
/* .is_relative_to https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.is_relative_to */
printf(" is_relative_to: %d\n", sp_is_relative_to(&p, &base));
printf(" (fluent) %d\n", SPF_PATH(p)->is_relative_to(&base));
/* .relative_to https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.relative_to */
tmp = sp_relative_to(&p, &base, false);
printf(" relative_to: %s\n", sp_str(&tmp));
tmp = SPF_PATH(p)->relative_to(&base, false)->path();
printf(" (fluent) %s\n", sp_str(&tmp));
/* .relative_to(walk_up=True) */
SpPath sibling = sp_path("/home/other/file.txt");
tmp = sp_relative_to(&sibling, &base, true);
printf(" walk_up: %s\n", sp_str(&tmp));
/* .match https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.match
* A function with several options takes a struct of them: zero-initialized options are pathlib's defaults */
SpMatchOptions no_options = {0};
printf(" match *.gz: %d\n", sp_match(&p, "*.gz", no_options));
printf(" (fluent) %d\n", SPF_PATH(p)->match("*.gz", no_options));
/* .full_match https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.full_match
* SP_CASE_DEFAULT is the flavor's: insensitive for Windows paths */
printf(" full_match: %d\n",
sp_match(&p, "/**/*.GZ", (SpMatchOptions){.full = true, .case_sensitive = SP_CASE_INSENSITIVE}));
printf(" (fluent) %d\n", SPF_PATH(p)->match("/**/*.gz", (SpMatchOptions){.full = true}));
/* ── Comparison ────────────────────────────────────────────── */
section("Comparison");
SpPath a = sp_path("/foo/bar"), b = sp_path("/foo/bar");
/* __eq__ / __ne__ */
printf(" eq: %d\n", sp_path_eq(&a, &b));
printf(" ne: %d\n", sp_path_ne(&a, &b));
printf(" (fluent eq) %d\n", SPF_PATH(a)->eq(&b));
printf(" (fluent ne) %d\n", SPF_PATH(a)->ne(&b));
printf(" cmp: %d\n", sp_path_cmp(&a, &b));
printf(" hash: %lu\n", sp_path_hash(&a));
/* ── String Conversion ─────────────────────────────────────── */
section("String Conversion");
/* .as_posix https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.as_posix */
printf(" as_posix: %s\n", sp_as_posix(&win).buf);
/* .as_uri https://docs.python.org/3/library/pathlib.html#pathlib.Path.as_uri
* Into your buffer: SP_ERR_TOO_LONG when it is too small, SP_ERR_NOT_ABSOLUTE for a relative path */
char uri_buf[SP_PATH_MAX * 3];
if (sp_as_uri(&p, uri_buf, sizeof(uri_buf)) == SP_OK)
printf(" as_uri: %s\n", uri_buf);
/* Path.from_uri https://docs.python.org/3/library/pathlib.html#pathlib.Path.from_uri */
tmp = sp_from_uri("file:///etc/hosts", SP_FLAVOR_POSIX);
printf(" from_uri: %s\n", sp_str(&tmp));
SpPath converted = sp_path_convert("C:\\Users\\dev", SP_FLAVOR_WINDOWS, SP_FLAVOR_POSIX);
printf(" convert: %s\n", sp_str(&converted));
/* ── Chaining ──────────────────────────────────────────────── */
section("Chaining");
SpPath built = SPF_P("/home/user")
->join("projects")->join("snakepath")
->with_name("libsnakepath.so")
->path();
printf(" %s\n", sp_str(&built));
SpTerm fname = SPF("/etc/nginx/nginx.conf")->parent()->name();
printf(" %s\n", fname.buf);
/* ── Filesystem ────────────────────────────────────────────── */
section("Filesystem");
/* Path.cwd https://docs.python.org/3/library/pathlib.html#pathlib.Path.cwd */
SpPath cwd = sp_cwd(SP_FLAVOR_NATIVE);
printf(" cwd: %s\n", sp_str(&cwd));
/* Path.home https://docs.python.org/3/library/pathlib.html#pathlib.Path.home */
SpPath home = sp_home(SP_FLAVOR_NATIVE);
printf(" home: %s\n", sp_str(&home));
/* .expanduser https://docs.python.org/3/library/pathlib.html#pathlib.Path.expanduser */
SpPath tilde = sp_path("~/docs");
tmp = sp_expanduser(&tilde);
printf(" expanduser: %s\n", sp_str(&tmp));
tmp = SPF_PATH(tilde)->expanduser()->path();
printf(" (fluent) %s\n", sp_str(&tmp));
/* .absolute https://docs.python.org/3/library/pathlib.html#pathlib.Path.absolute */
SpPath dot = sp_path(".");
tmp = sp_absolute(&dot);
printf(" absolute: %s\n", sp_str(&tmp));
/* .resolve https://docs.python.org/3/library/pathlib.html#pathlib.Path.resolve */
tmp = sp_resolve(&dot, false);
printf(" resolve: %s\n", sp_str(&tmp));
/* ── Type Checks ───────────────────────────────────────────── */
section("Type Checks");
/* .exists https://docs.python.org/3/library/pathlib.html#pathlib.Path.exists
* One function for exists and the is_* predicates, with pathlib's follow_symlinks */
printf(" exists: %d\n", sp_is(&cwd, SP_ANY, true));
printf(" (fluent) %d\n", SPF_PATH(cwd)->is(SP_ANY, true));
/* .is_dir https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_dir
* exists, is_dir and is_file take follow_symlinks */
printf(" is_dir: %d\n", sp_is(&cwd, SP_DIR, true));
/* .is_file https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_file */
printf(" is_file: %d\n", sp_is(&cwd, SP_FILE, true));
/* .is_symlink https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_symlink */
printf(" is_symlink: %d\n", sp_is(&cwd, SP_SYMLINK, false));
/* Also: SP_BLOCK_DEVICE, SP_CHAR_DEVICE, SP_FIFO, SP_SOCKET, SP_MOUNT, SP_JUNCTION */
/* .stat https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat */
/* follow_symlinks false is lstat */
SpStatResult st = sp_stat(&cwd, true);
printf(" stat size: %lld\n", st.sp_size);
/* ── File I/O ──────────────────────────────────────────────── */
section("File I/O");
SpPath tmpdir = sp_join_one(&cwd, "demo_tmp");
SpPath subdir = sp_join_one(&tmpdir, "sub");
SpPath tmpfile = sp_join_one(&tmpdir, "hello.txt");
/* .mkdir https://docs.python.org/3/library/pathlib.html#pathlib.Path.mkdir */
check("mkdir:", sp_mkdir(&subdir, 0755, (SpMkdirOptions){.parents = true, .exist_ok = true, .parent_mode = SP_MODE_DIR}));
/* .touch https://docs.python.org/3/library/pathlib.html#pathlib.Path.touch */
check("touch:", sp_touch(&tmpfile, SP_MODE_FILE, true));
/* .write_bytes https://docs.python.org/3/library/pathlib.html#pathlib.Path.write_bytes */
const char *msg = "Hello, snakepath!";
SpIOResult wr = sp_write_file(&tmpfile, msg, strlen(msg));
printf(" write: %zu bytes (%s)\n", wr.bytes, sp_error_str(wr.error));
/* .read_bytes https://docs.python.org/3/library/pathlib.html#pathlib.Path.read_bytes */
char buf[256];
SpIOResult rd = sp_read_file(&tmpfile, buf, sizeof(buf));
buf[rd.bytes] = '\0';
printf(" read: \"%s\"\n", buf);
/* .rename https://docs.python.org/3/library/pathlib.html#pathlib.Path.rename */
SpPath renamed = sp_join_one(&tmpdir, "renamed.txt");
check("rename:", sp_rename(&tmpfile, &renamed, false).error); /* replace: true */
/* .copy https://docs.python.org/3/library/pathlib.html#pathlib.Path.copy (recursive for directories) */
SpPath copied = sp_join_one(&tmpdir, "copy.txt");
check("copy:", sp_copy(&renamed, &copied, (SpCopyOptions){.follow_symlinks = true}).error);
/* .move_into https://docs.python.org/3/library/pathlib.html#pathlib.Path.move_into: move with into (copy_into is
* copy's .into). move renames, or copies and deletes across filesystems */
SpPath moved = sp_move(&copied, &subdir, true);
printf(" move_into: %s\n", sp_str(&moved));
tmp = SPF_PATH(moved)->move(&copied, false)->path();
printf(" (fluent move) %s\n", sp_str(&tmp));
/* .chmod https://docs.python.org/3/library/pathlib.html#pathlib.Path.chmod */
check("chmod:", sp_chmod(&renamed, 0600, true));
/* .owner https://docs.python.org/3/library/pathlib.html#pathlib.Path.owner (SP_ERR_UNSUPPORTED on Windows) */
SpTerm owner = sp_owner(&renamed, true);
printf(" owner: %s\n", owner.error == SP_OK ? owner.buf : sp_error_str(owner.error));
/* .group https://docs.python.org/3/library/pathlib.html#pathlib.Path.group */
SpTerm group = SPF_PATH(renamed)->group(true);
printf(" (fluent group) %s\n", group.error == SP_OK ? group.buf : sp_error_str(group.error));
/* ── Symlinks ──────────────────────────────────────────────── */
section("Symlinks");
SpPath link = sp_join_one(&tmpdir, "link.txt");
/* .symlink_to https://docs.python.org/3/library/pathlib.html#pathlib.Path.symlink_to (hardlink_to: .hard) */
check("symlink:", sp_link_to(&link, &renamed, (SpLinkOptions){0}));
/* .readlink https://docs.python.org/3/library/pathlib.html#pathlib.Path.readlink */
tmp = sp_readlink(&link);
printf(" readlink: %s\n", sp_str(&tmp));
/* .samefile https://docs.python.org/3/library/pathlib.html#pathlib.Path.samefile */
printf(" samefile: %d\n", sp_samefile(&link, &renamed));
printf(" (fluent) %d\n", SPF_PATH(link)->samefile(&renamed));
/* ── Glob & Iterdir ────────────────────────────────────────── */
section("Glob & Iterdir");
/* .iterdir https://docs.python.org/3/library/pathlib.html#pathlib.Path.iterdir */
SP_ITERDIR_FOREACH(&tmpdir, entry)
printf(" iterdir: %s\n", sp_str(&entry));
/* .info https://docs.python.org/3/library/pathlib.html#pathlib.Path.info
* exists, is_dir, is_file and is_symlink, each of stat and lstat asked at most once; a listed entry's info starts
* with what the listing said, as os.scandir's does, so these ask the OS nothing */
SpIterdirIter listing = sp_iterdir_begin(&tmpdir);
for (SpPath entry; sp_iterdir_next(&listing, &entry);) {
SpPathInfo info = listing.info;
printf(" info: %s is_dir=%d\n", sp_str(&entry), sp_info(&entry, &info, SP_DIR, true));
}
sp_iterdir_end(&listing);
/* .glob https://docs.python.org/3/library/pathlib.html#pathlib.Path.glob */
SP_GLOB_FOREACH(&tmpdir, "*.txt", (SpGlobOptions){0}, match)
printf(" glob: %s\n", sp_str(&match));
/* sp_glob_begin/next/end: the options, and it.error for a bad pattern or a limit */
SpGlobIter git = sp_glob_begin(&tmpdir, "**/", (SpGlobOptions){.case_sensitive = SP_CASE_SENSITIVE});
for (SpPath dir; sp_glob_next(&git, &dir);)
printf(" glob **/: %s\n", sp_str(&dir));
sp_glob_end(&git);
/* .rglob https://docs.python.org/3/library/pathlib.html#pathlib.Path.rglob: glob with .recursive */
SP_GLOB_FOREACH(&tmpdir, "*.txt", (SpGlobOptions){.recursive = true}, match)
printf(" rglob: %s\n", sp_str(&match));
/* ── Walk ──────────────────────────────────────────────────── */
section("Walk");
/* .walk https://docs.python.org/3/library/pathlib.html#pathlib.Path.walk
* An iterator over (dirpath, dirnames, filenames), keeping the names in your buffer. Top-down, drop names from
* dirnames (or lower dirname_count) to prune. */
static char names[1 << 16];
SpWalkIter walk = sp_walk_begin(&tmpdir, (SpWalkOptions){0}, names, sizeof(names));
for (SpWalkEntry *e; (e = sp_walk_next(&walk)) != NULL;)
printf(" %s/ (%zu dirs, %zu files)\n", sp_str(&e->dirpath), e->dirname_count, e->filename_count);
check("walk:", walk.error);
sp_walk_end(&walk); /* releases the directory handles of a walk stopped early */
/* ── Error Handling ────────────────────────────────────────── */
section("Error Handling");
/* A failed path carries its error in .error, and passes it on: check once, at the end */
SpPath no_name = sp_path("/");
SpPath err = sp_with_name(&no_name, "x");
err = sp_join_one(&err, "more");
printf(" with_name(\"/\", \"x\") / \"more\": %s\n", sp_error_str(err.error));
tmp = SPF("/")->with_name("x")->join("more")->path();
printf(" (fluent) %s\n", sp_error_str(tmp.error));
/* .unlink and .rmdir are sp_remove, with .missing_ok and .dir; .recursive removes a tree (Path._delete) */
SpRemoveOptions file = {0};
SpRemoveOptions dir = {.dir = true};
check("unlink:", sp_remove(&link, file));
check("unlink:", sp_remove(&copied, file));
check("unlink:", sp_remove(&renamed, file));
check("rmdir:", sp_remove(&tmpdir, dir)); /* not empty yet */
check("delete:", sp_remove(&tmpdir, (SpRemoveOptions){.recursive = true}));
return 0;
}