Codync
免费开源的 1:1 Grok Bot 与 Muse 替代方案。可在 iPhone、Mac 或 Linux 桌面上将 Claude 代码、Codex、Cursor 以及 40 多个编码代理作为持久机器人使用。支持群组聊天、线程、应用。
AI 翻译
尚未收集仓库信息。
尚未检测到更新。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;
}